Regulatory Compliance Overview
The Baanx Consent Management API is designed to help you meet data privacy regulations including GDPR, CCPA, and other regional requirements. This guide covers compliance considerations and best practices.
This guide provides technical implementation guidance. Consult with legal counsel to ensure your specific implementation meets all applicable regulatory requirements.
GDPR Compliance
The General Data Protection Regulation (GDPR) applies to organizations processing personal data of EU residents.
Key GDPR Requirements
Lawful Basis Establish legal basis for data processing (consent, contract, legitimate interest)
Explicit Consent Consent must be freely given, specific, informed, and unambiguous
Right to Withdraw Users can withdraw consent as easily as they gave it
Records of Consent Maintain proof of when and how consent was obtained
GDPR-Compliant Consent Collection
Clear Communication
Inform users before data collection about:
What data you’re collecting
Why you’re collecting it
How long you’ll retain it
Who you’ll share it with
Their rights (access, rectification, erasure, portability)
Granular Consent
Separate consent for different purposes: const consents = [
{ type: 'termsAndPrivacy' , status: 'granted' }, // Required
{ type: 'marketingNotifications' , status: 'denied' }, // Optional
{ type: 'smsNotifications' , status: 'denied' }, // Optional
{ type: 'emailNotifications' , status: 'granted' } // Optional
];
Users must be able to opt-in to marketing separately from service requirements.
Affirmative Action
Require explicit opt-in (no pre-checked boxes): // ✅ Good: User must actively check
< Checkbox checked = { marketingConsent } onChange = { setMarketingConsent } >
I agree to receive marketing communications
</ Checkbox >
// ❌ Bad: Pre-checked by default
< Checkbox checked = { true } onChange = { setMarketingConsent } >
I agree to receive marketing communications
</ Checkbox >
Record Keeping
Capture comprehensive metadata: metadata : {
ipAddress : req . ip ,
userAgent : req . headers [ 'user-agent' ],
timestamp : new Date (). toISOString (),
consentMethod : 'registration_form' ,
privacyPolicyVersion : 'v2.1' ,
termsVersion : 'v3.0'
}
Easy Withdrawal
Implement straightforward revocation: async function revokeMarketingConsent ( userId : string ) {
const { consentSets } = await consentManager . getUserConsentStatus (
userId ,
true
);
const marketingConsent = consentSets [ 0 ]. consents . find (
c => c . consentType === 'marketingNotifications' && c . consentStatus === 'granted'
);
if ( marketingConsent ) {
await consentManager . revokeConsent (
consentSets [ 0 ]. consentSetId ,
marketingConsent . consentId
);
}
}
Right to Be Forgotten (GDPR Article 17)
When users request data deletion:
Revoke all consents :
const { consentSets } = await consentManager . getUserConsentStatus ( userId , true );
for ( const consentSet of consentSets ) {
for ( const consent of consentSet . consents ) {
if ( consent . consentStatus === 'granted' ) {
await consentManager . revokeConsent (
consentSet . consentSetId ,
consent . consentId
);
}
}
}
Export audit trail (for your records):
const auditTrail = await consentManager . getAuditTrail ( userId , 1000 , 0 );
await saveAuditForLegalHold ( userId , auditTrail );
Process deletion in your systems (user data, accounts, etc.)
The Baanx API maintains consent records even after revocation for regulatory compliance. This is permissible under GDPR as legitimate interest for legal defense.
CCPA Compliance
The California Consumer Privacy Act (CCPA) applies to businesses collecting personal information of California residents.
Key CCPA Requirements
Requirement Implementation Notice at Collection Inform users at/before data collection what categories of data you’re collecting and why Opt-Out of Sale Provide “Do Not Sell My Personal Information” option Access & Deletion Enable users to request their data and deletion Non-Discrimination Don’t penalize users who exercise privacy rights
CCPA-Compliant Implementation
Notice at Collection
Display clear privacy notice:
< ConsentForm >
< PrivacyNotice >
We collect personal information including name , email , phone number , and
location data to provide financial services . View our full privacy policy
for details on data categories , purposes , and third - party sharing .
</ PrivacyNotice >
< ConsentCheckbox type = "termsAndPrivacy" required />
< ConsentCheckbox type = "emailNotifications" optional />
</ ConsentForm >
Opt-Out Mechanisms
Implement opt-out for data sales/sharing:
const consents = [
{ type: 'termsAndPrivacy' , status: 'granted' },
{ type: 'marketingNotifications' , status: 'denied' }, // Opt-out of marketing
{ type: 'emailNotifications' , status: 'denied' } // Opt-out of emails
];
The denied status in consent records documents the user’s opt-out decision for regulatory compliance.
Audit Trail Management
The API maintains an immutable audit trail for all consent changes. Use this for regulatory compliance and investigations.
What’s Recorded in Audit Trail
Each audit record contains:
{
"auditId" : "audit_001" ,
"action" : "created | updated | revoked" ,
"timestamp" : "ISO 8601 date-time" ,
"consentSetId" : "UUID of consent set" ,
"changes" : {
"before" : {
"consentType" : "marketingNotifications" ,
"consentStatus" : "granted"
},
"after" : {
"consentType" : "marketingNotifications" ,
"consentStatus" : "revoked"
}
},
"metadata" : {
"ipAddress" : "192.168.1.1" ,
"userAgent" : "Mozilla/5.0..." ,
"timestamp" : "2024-01-15T10:30:00Z"
}
}
Audit Trail Use Cases
Export complete consent history for regulatory examinations: async function exportConsentAudit ( userId : string ) {
let allRecords = [];
let offset = 0 ;
const limit = 100 ;
while ( true ) {
const { auditRecords , pagination } = await consentManager . getAuditTrail (
userId ,
limit ,
offset
);
allRecords . push ( ... auditRecords );
if ( offset + limit >= pagination . total ) break ;
offset += limit ;
}
return generateAuditReport ( userId , allRecords );
}
Data Subject Access Requests (DSAR)
Users can request their consent history: async function handleDSAR ( userId : string ) {
const { consentStatus , consentSets } = await consentManager . getUserConsentStatus (
userId ,
true
);
const auditTrail = await consentManager . getAuditTrail ( userId , 1000 , 0 );
return {
currentStatus: consentStatus ,
consentSets: consentSets ,
changeHistory: auditTrail . auditRecords
};
}
Prove consent was obtained correctly in case of disputes: async function proveLawfulConsent ( userId : string , disputeDate : string ) {
const { auditRecords } = await consentManager . getAuditTrail ( userId );
const relevantRecords = auditRecords . filter (
record => new Date ( record . timestamp ) <= new Date ( disputeDate )
);
return {
consentObtained: relevantRecords . some (
r => r . action === 'created' && r . changes . after . consentStatus === 'granted'
),
evidence: relevantRecords
};
}
Generate periodic compliance reports: async function generateComplianceReport ( startDate : string , endDate : string ) {
// Query all users who completed registration in date range
const users = await getUsersRegisteredBetween ( startDate , endDate );
const report = {
totalUsers: users . length ,
completeConsents: 0 ,
incompleteConsents: 0 ,
revocations: 0
};
for ( const user of users ) {
const { consentStatus } = await consentManager . getUserConsentStatus ( user . id );
if ( consentStatus === 'complete' ) report . completeConsents ++ ;
else report . incompleteConsents ++ ;
const { auditRecords } = await consentManager . getAuditTrail ( user . id );
report . revocations += auditRecords . filter (
r => r . action === 'revoked'
). length ;
}
return report ;
}
Best Practices
1. Version Your Policies
Track policy versions in metadata to prove which version users consented to:
metadata : {
privacyPolicyVersion : 'v2.1' ,
termsOfServiceVersion : 'v3.0' ,
policyUrl : 'https://example.com/privacy-v2.1' ,
timestamp : new Date (). toISOString ()
}
When policies change significantly, require re-consent:
async function checkPolicyVersions ( userId : string ) {
const { consentSets } = await consentManager . getUserConsentStatus ( userId , true );
const latestConsent = consentSets [ 0 ];
const consentedVersion = latestConsent . consents [ 0 ]. metadata ?. privacyPolicyVersion ;
const currentVersion = 'v3.0' ;
if ( consentedVersion !== currentVersion ) {
return { requiresReconsent: true , reason: 'policy_update' };
}
return { requiresReconsent: false };
}
2. Implement Consent Expiry
Consider re-confirming consent periodically:
async function checkConsentAge ( userId : string , maxAgeMonths : number = 24 ) {
const { consentSets } = await consentManager . getUserConsentStatus ( userId , true );
const latestConsent = consentSets [ 0 ];
const consentDate = new Date ( latestConsent . createdAt );
const now = new Date ();
const monthsSinceConsent = ( now . getTime () - consentDate . getTime ()) / ( 1000 * 60 * 60 * 24 * 30 );
if ( monthsSinceConsent > maxAgeMonths ) {
return { requiresReconsent: true , reason: 'consent_expired' };
}
return { requiresReconsent: false };
}
3. Separate Required vs Optional Consents
Make it clear which consents are necessary for service:
const consentConfig = {
required: [ 'termsAndPrivacy' , 'eSignAct' ],
optional: [ 'marketingNotifications' , 'smsNotifications' , 'emailNotifications' ]
};
function validateConsents ( consents : ConsentItem []) {
const requiredGranted = consentConfig . required . every ( type =>
consents . some ( c => c . consentType === type && c . consentStatus === 'granted' )
);
if ( ! requiredGranted ) {
throw new Error ( 'Required consents must be granted' );
}
return true ;
}
UI Example:
< ConsentForm >
< RequiredSection >
< h3 > Required for Service </ h3 >
< Checkbox required > Terms of Service & Privacy Policy </ Checkbox >
< Checkbox required > Electronic Signature Agreement </ Checkbox >
</ RequiredSection >
< OptionalSection >
< h3 > Optional </ h3 >
< Checkbox > Marketing Communications (you can opt out anytime) </ Checkbox >
< Checkbox > SMS Notifications </ Checkbox >
< Checkbox > Email Notifications </ Checkbox >
</ OptionalSection >
</ ConsentForm >
4. Provide Consent Management UI
Build user-facing consent preferences:
function ConsentPreferences ({ userId } : { userId : string }) {
const [ consents , setConsents ] = useState < ConsentSetDetail []>([]);
useEffect (() => {
const fetchConsents = async () => {
const { consentSets } = await consentManager . getUserConsentStatus (
userId ,
true
);
setConsents ( consentSets );
};
fetchConsents ();
}, [ userId ]);
const handleToggle = async ( consentSetId : string , consentId : string , currentStatus : string ) => {
if ( currentStatus === 'granted' ) {
await consentManager . revokeConsent ( consentSetId , consentId );
} else {
// Create new consent record with 'granted' status
// (requires new endpoint or re-consent flow)
}
// Refresh consents
};
return (
< Card >
< h2 > Privacy Preferences </ h2 >
{ consents [0]?. consents . map ( consent => (
< ConsentToggle
key = {consent. consentId }
consent = { consent }
onToggle = {() => handleToggle (
consents [ 0 ].consentSetId,
consent.consentId,
consent.consentStatus
)}
/>
))}
</ Card >
);
}
5. Log All Consent Operations
Maintain application-level logs for consent operations:
async function createConsentWithLogging (
onboardingId : string ,
consents : ConsentItem [],
metadata : any
) {
logger . info ( 'Creating consent set' , {
onboardingId ,
consentTypes: consents . map ( c => c . consentType ),
timestamp: new Date (). toISOString ()
});
try {
const result = await consentManager . createOnboardingConsent (
onboardingId ,
consents ,
metadata
);
logger . info ( 'Consent set created successfully' , {
consentSetId: result . consentSetId ,
onboardingId
});
return result ;
} catch ( error ) {
logger . error ( 'Consent creation failed' , {
onboardingId ,
error: error . message
});
throw error ;
}
}
6. Handle Incomplete Consent Gracefully
Guide users to complete required consents:
async function checkAndEnforceConsent ( userId : string ) {
const { consentStatus , consentSets } = await consentManager . getUserConsentStatus (
userId ,
true
);
if ( consentStatus === 'incomplete' ) {
const missingConsents = identifyMissingConsents ( consentSets [ 0 ]);
return {
allowed: false ,
reason: 'incomplete_consent' ,
action: 'redirect_to_consent_page' ,
missingConsents
};
}
return { allowed: true };
}
function identifyMissingConsents ( consentSet : ConsentSetDetail ) {
const requiredTypes = consentSet . policyType === 'US'
? [ 'eSignAct' , 'termsAndPrivacy' , 'marketingNotifications' , 'smsNotifications' , 'emailNotifications' ]
: [ 'termsAndPrivacy' , 'marketingNotifications' , 'smsNotifications' , 'emailNotifications' ];
const grantedTypes = consentSet . consents
. filter ( c => c . consentStatus === 'granted' )
. map ( c => c . consentType );
return requiredTypes . filter ( type => ! grantedTypes . includes ( type ));
}
Data Retention
Consent Record Retention
Active consent records : Retain indefinitely while user account is active
Revoked consents : Retain for legal defense (typically 7 years minimum)
Audit trail : Retain according to regulatory requirements (GDPR: 6 years, CCPA: varies)
Deletion After Account Closure
When a user closes their account:
Retain consent audit trail for legal hold period
Mark records as “account closed” but don’t delete
After legal hold expires, anonymize consent records (remove PII from metadata)
async function handleAccountClosure ( userId : string ) {
const auditTrail = await consentManager . getAuditTrail ( userId , 1000 , 0 );
await archiveForLegalHold ( userId , auditTrail , {
retentionYears: 7 ,
reason: 'account_closure'
});
scheduleAnonymization ( userId , addYears ( new Date (), 7 ));
}
Regional Considerations
US-Specific Requirements
Use policyType: "US" for US-based users
Requires eSignAct consent for E-Sign Act compliance
Consider state-specific laws (CCPA in California, VCDPA in Virginia, etc.)
All 5 consent types required
EU-Specific Requirements
Use policyType: "global" for EU users
4 consent types (excludes eSignAct)
Implement “Right to Object” for automated processing
Provide data portability mechanisms
Multi-Region Deployments
Use the x-us-env header to route requests correctly:
const headers = {
'x-client-key' : clientKey ,
'x-secret-key' : secretKey
};
if ( userRegion === 'US' ) {
headers [ 'x-us-env' ] = 'true' ;
}
await fetch ( 'https://api.baanx.com/v2/consent/onboarding' , {
method: 'POST' ,
headers ,
body: JSON . stringify ( requestBody )
});
Next Steps