Consent Management
Comprehensive guide to managing user consent for GDPR, COPPA, and other privacy regulations.
Why Consent Management Matters
- Legal Compliance: Required by GDPR, COPPA, CCPA, and other laws
- User Trust: Transparent consent builds trust with users
- Better Monetization: Proper consent = more ad inventory = higher revenue
- Platform Requirements: Google, Apple, and stores require it
Consent Management Flow
// Complete Consent Management Flow
1. App Launch
↓
2. Check if consent is required
• EU users? → GDPR consent needed
• Child user? → COPPA mode needed
• Other regions? → Optional but recommended
↓
3. Check if consent already obtained
• Yes → Use stored consent
• No → Show consent dialog
↓
4. User makes choice
• Accept → Personalized ads allowed
• Decline → Contextual ads only
• (For kids: Always contextual only)
↓
5. Store consent choice
• localStorage or backend
• Include timestamp
• Allow user to change later
↓
6. Initialize SDK with consent
• Pass consent flags to init()
• SDK respects user choice
↓
7. Show ads accordingly
• Personalized (if consented)
• Contextual (if declined/child)
Implementation Options
Option 1: Simple Consent Dialog (DIY)
Build your own consent UI. Best for simple apps.
class SimpleConsentManager {
constructor() {
this.consentKey = 'user_consent';
}
async checkAndRequestConsent() {
// Check if consent already given
const stored = localStorage.getItem(this.consentKey);
if (stored) {
return JSON.parse(stored);
}
// Check if user needs consent (EU user)
const needsConsent = await this.isEUUser();
if (!needsConsent) {
// Non-EU user, default to consented
return { gdprApplies: false, gdprConsent: true };
}
// Show consent dialog
const consent = await this.showConsentDialog();
// Store consent
localStorage.setItem(this.consentKey, JSON.stringify({
gdprApplies: true,
gdprConsent: consent,
timestamp: Date.now()
}));
return { gdprApplies: true, gdprConsent: consent };
}
async isEUUser() {
try {
const res = await fetch('https://ipapi.co/json/');
const data = await res.json();
const euCountries = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE','GB'];
return euCountries.includes(data.country_code);
} catch {
return false;
}
}
showConsentDialog() {
return new Promise((resolve) => {
// Create dialog
const dialog = document.createElement('div');
dialog.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 20px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
z-index: 9999;
`;
dialog.innerHTML = `
Your Privacy Matters
We use cookies and collect data to provide personalized ads.
You can choose to accept or decline.
Privacy Policy
`;
document.body.appendChild(dialog);
document.getElementById('accept-btn').onclick = () => {
document.body.removeChild(dialog);
resolve(true);
};
document.getElementById('decline-btn').onclick = () => {
document.body.removeChild(dialog);
resolve(false);
};
});
}
clearConsent() {
localStorage.removeItem(this.consentKey);
}
}
// Usage
const consentManager = new SimpleConsentManager();
async function initApp() {
const consent = await consentManager.checkAndRequestConsent();
// Initialize SDK with consent
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: "user_123",
gdprApplies: consent.gdprApplies,
gdprConsent: consent.gdprConsent,
onReward: function(reward) {
grantCoins(100);
}
});
}
initApp();
Option 2: Consent Management Platform (CMP)
Use a third-party CMP for compliance. Recommended for complex apps.
Popular CMPs:
- Cookiebot: Easy integration, multi-language
- OneTrust: Enterprise-grade, comprehensive
- Termly: Free tier available, simple
- Quantcast Choice: IAB TCF 2.0 compliant
Example: Cookiebot Integration
// Add Cookiebot script to your HTML
//
// Wait for Cookiebot to load
window.addEventListener('CookiebotOnLoad', function() {
const gdprApplies = Cookiebot.regulations.gdprApplies;
const gdprConsent = Cookiebot.consent.marketing;
// Initialize SDK with Cookiebot consent
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: "user_123",
gdprApplies: gdprApplies,
gdprConsent: gdprConsent,
onReward: function(reward) {
grantCoins(100);
}
});
});
// Listen for consent changes
window.addEventListener('CookiebotOnAccept', function() {
console.log('User accepted marketing consent');
// Reinitialize SDK if needed
});
window.addEventListener('CookiebotOnDecline', function() {
console.log('User declined marketing consent');
// Switch to contextual ads only
});
Complete Consent Manager
Production-ready consent management with both GDPR and COPPA:
class AdvancedConsentManager {
constructor() {
this.consentKey = 'bzze_consent_data';
this.consentData = this.loadConsent();
}
loadConsent() {
const stored = localStorage.getItem(this.consentKey);
if (stored) {
return JSON.parse(stored);
}
return null;
}
saveConsent(data) {
localStorage.setItem(this.consentKey, JSON.stringify({
...data,
timestamp: Date.now(),
version: '1.0'
}));
this.consentData = data;
}
async initialize() {
// Check if we need to re-request consent (30 days expired)
if (this.consentData) {
const age = Date.now() - this.consentData.timestamp;
const thirtyDays = 30 * 24 * 60 * 60 * 1000;
if (age < thirtyDays) {
return this.consentData;
}
}
// Get user location and age
const [isEU, isChild] = await Promise.all([
this.checkEUUser(),
this.checkChildUser()
]);
let consent = {
gdprApplies: isEU,
gdprConsent: !isEU, // Default true for non-EU
coppaCompliant: true,
isChildDirected: isChild
};
// Request consent if needed
if (isEU && !isChild) {
consent.gdprConsent = await this.requestGDPRConsent();
}
if (isChild) {
// Children always get contextual ads only
consent.gdprConsent = false;
consent.isChildDirected = true;
}
this.saveConsent(consent);
return consent;
}
async checkEUUser() {
try {
const res = await fetch('https://ipapi.co/json/');
const data = await res.json();
const euCountries = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE','GB','NO','IS','LI'];
return euCountries.includes(data.country_code);
} catch {
return false;
}
}
async checkChildUser() {
const storedAge = localStorage.getItem('userAge');
if (storedAge) {
return parseInt(storedAge) < 13;
}
// Show age gate
const birthYear = await this.showAgeGate();
if (!birthYear) return false;
const age = new Date().getFullYear() - birthYear;
localStorage.setItem('userAge', age);
return age < 13;
}
showAgeGate() {
return new Promise((resolve) => {
const dialog = document.createElement('div');
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 10000;
text-align: center;
`;
dialog.innerHTML = `
Welcome!
Please enter your birth year to continue:
`;
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 9999;
`;
document.body.appendChild(overlay);
document.body.appendChild(dialog);
document.getElementById('submit-age-btn').onclick = () => {
const year = parseInt(document.getElementById('birth-year-input').value);
if (year && year >= 1900 && year <= new Date().getFullYear()) {
document.body.removeChild(overlay);
document.body.removeChild(dialog);
resolve(year);
} else {
alert('Please enter a valid year');
}
};
});
}
requestGDPRConsent() {
return new Promise((resolve) => {
const dialog = document.createElement('div');
dialog.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 20px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
z-index: 9999;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
🍪 Your Privacy Matters
We use cookies and collect data to provide personalized ads and improve your experience.
You can choose to accept or decline personalized advertising.
Privacy Policy |
Cookie Policy
`;
document.body.appendChild(dialog);
document.getElementById('accept-all-btn').onclick = () => {
document.body.removeChild(dialog);
resolve(true);
};
document.getElementById('decline-btn').onclick = () => {
document.body.removeChild(dialog);
resolve(false);
};
});
}
getConsent() {
return this.consentData;
}
resetConsent() {
localStorage.removeItem(this.consentKey);
localStorage.removeItem('userAge');
this.consentData = null;
}
}
// Usage
const consentManager = new AdvancedConsentManager();
async function startApp() {
const consent = await consentManager.initialize();
console.log('Consent obtained:', consent);
// Initialize BZZE Ads SDK with consent
RewardedAd.init({
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
userId: "user_" + Math.random().toString(36).substr(2, 9),
// GDPR
gdprApplies: consent.gdprApplies,
gdprConsent: consent.gdprConsent,
// COPPA
coppaCompliant: consent.coppaCompliant,
isChildDirected: consent.isChildDirected,
onReward: function(reward) {
console.log('Reward granted!');
grantCoins(100);
},
onError: function(error) {
console.error('Ad error:', error);
}
});
// Start your game
startGame();
}
// Initialize app
startApp();
Consent Storage
What to Store
const consentData = {
// GDPR
gdprApplies: true, // Is user in EU?
gdprConsent: true, // Did user consent?
// COPPA
coppaCompliant: true, // Is app COPPA compliant?
isChildDirected: false, // Is user a child?
// Metadata
timestamp: 1234567890, // When consent was given
version: '1.0', // Consent version
consentString: 'base64...' // IAB TCF string (if using)
};
Storage Options
| Method | Pros | Cons |
|---|---|---|
localStorage |
Simple, fast, client-side | Cleared if user clears browser data |
| Backend Database | Persistent, auditable, secure | Requires server, user account |
| CMP Service | Fully managed, compliant | External dependency, cost |
Allowing Users to Change Consent
Users must be able to withdraw or modify consent at any time:
// Add "Privacy Settings" button to your app
function showPrivacySettings() {
const consent = consentManager.getConsent();
const dialog = `
Privacy Settings
Personalized ads are based on your activity and help support free content.
`;
// Show dialog
document.body.insertAdjacentHTML('beforeend', dialog);
}
function savePrivacySettings() {
const newConsent = document.getElementById('personalized-ads').checked;
// Update consent
consentManager.saveConsent({
...consentManager.getConsent(),
gdprConsent: newConsent
});
// Notify user
alert('Privacy settings updated!');
// Close dialog
closePrivacySettings();
// Optionally: Reload app with new settings
location.reload();
}
Best Practices
- Request consent before loading any ads or trackers
- Use clear, simple language in consent dialogs
- Store consent timestamp and version
- Allow users to change consent anytime
- Re-request consent every 30 days (recommended)
- Test consent flow thoroughly
- Document your consent process
- Pre-check consent boxes
- Make consent mandatory for app access (unless legal basis)
- Hide consent options in complex menus
- Use dark patterns to trick users into consenting
- Ignore consent choices
- Forget to handle consent withdrawal
Testing Your Consent Flow
// Testing checklist
function testConsentFlow() {
console.log('🧪 Testing Consent Flow...');
// 1. Clear existing consent
consentManager.resetConsent();
console.log('✅ Consent cleared');
// 2. Reload page to trigger consent flow
// (Manually test consent dialog appearance)
// 3. Test both Accept and Decline paths
// (Manually test both options)
// 4. Verify consent is stored
const consent = consentManager.getConsent();
console.log('Stored consent:', consent);
// 5. Verify SDK receives correct flags
const info = RewardedAd.getInfo();
console.log('SDK config:', info.config);
// 6. Test consent withdrawal
// (Add "Privacy Settings" button to UI)
console.log('✅ Consent flow test complete');
}
// Run test
testConsentFlow();