Events & Callbacks
React to consent changes with Zest events and callbacks
Overview
Zest provides three ways to react to consent changes:
- DOM Events - Standard browser events you can listen to anywhere
Zest.on()/Zest.once()subscribers (v2.0.0+) - Cleaner API with explicit unsubscribe- Callbacks - Functions defined in your configuration
DOM Events
zest:ready
Fired when Zest has initialized and determined the current consent state.
document.addEventListener('zest:ready', (e) => {
console.log('Zest initialized');
console.log('Current consent:', e.detail.consent);
// { essential: true, functional: false, analytics: false, marketing: false }
});zest:consent
Fired when the user accepts cookies (either all or selected categories).
document.addEventListener('zest:consent', (e) => {
console.log('User consented');
console.log('Categories:', e.detail.consent);
console.log('Previous state:', e.detail.previous);
if (e.detail.consent.analytics) {
initializeAnalytics();
}
if (e.detail.consent.marketing) {
initializeMarketing();
}
});zest:reject
Fired when the user rejects all non-essential cookies.
document.addEventListener('zest:reject', (e) => {
console.log('User rejected all');
console.log('Consent state:', e.detail.consent);
// { essential: true, functional: false, analytics: false, marketing: false }
});zest:change
Fired on any consent change (accept, reject, or modification).
document.addEventListener('zest:change', (e) => {
console.log('Consent changed');
console.log('New state:', e.detail.consent);
console.log('Previous state:', e.detail.previous);
// Check what changed
const prev = e.detail.previous;
const curr = e.detail.consent;
if (prev.analytics !== curr.analytics) {
console.log('Analytics consent changed to:', curr.analytics);
}
});zest:show
Fired when the banner or modal is displayed.
document.addEventListener('zest:show', (e) => {
console.log('UI shown:', e.detail.type); // 'banner' or 'modal'
});zest:hide
Fired when the banner or modal is hidden.
document.addEventListener('zest:hide', (e) => {
console.log('UI hidden:', e.detail.type); // 'banner' or 'modal'
});zest:geo
Since v2.4.0.
Fired once geo resolution completes (only when geo is configured). The detail carries the chosen action and the sanitized jurisdiction verdict (null if resolution failed and the fallback action was applied).
document.addEventListener('zest:geo', (e) => {
console.log('Action:', e.detail.action);
// 'consent' | 'notice' | 'allow' | 'block'
console.log('Verdict:', e.detail.verdict);
// { isEU, isEEA, isGDPR, isCCPA, isUSPrivacy, regulations: [...] } or null
});This is the primary hook for the headless build, which renders no UI — show your own banner on 'consent', your own "Do Not Sell" link on 'notice'.
Subscriber API
Since v2.0.0.
Zest.on() and Zest.once() are thin wrappers around the DOM events above that return an unsubscribe function and let you omit the zest: prefix. Handy when you want a clean teardown path (e.g. inside a SPA component).
Zest.on(event, callback)
const unsubscribe = Zest.on('consent', (consent) => {
if (consent.analytics) initAnalytics();
});
// Later, stop listening
unsubscribe();The event name accepts both forms:
Zest.on('change', handler); // short form
Zest.on('zest:change', handler); // full event name
Zest.on(Zest.EVENTS.CHANGE, handler); // via constantsZest.once(event, callback)
Same signature, but the callback fires at most once and then auto-unsubscribes:
Zest.once('ready', (consent) => {
console.log('Zest is ready, current consent:', consent);
});Unsubscribing everywhere
Every subscriber — DOM event, on, once, callback — receives the same payload shape. Pick the style that fits your codebase; you don't have to be consistent.
// In a React component, on/off cleanly
useEffect(() => {
const unsubscribe = Zest.on('change', (consent) => {
setConsent(consent);
});
return unsubscribe;
}, []);Configuration Callbacks
Define callbacks in your ZestConfig for a cleaner setup:
window.ZestConfig = {
callbacks: {
onReady: (consent) => {
console.log('Ready with consent:', consent);
},
onAccept: (consent) => {
console.log('Accepted:', consent);
if (consent.analytics) {
gtag('config', 'GA-XXXXX');
}
},
onReject: () => {
console.log('All rejected');
},
onChange: (consent) => {
console.log('Changed to:', consent);
}
}
};Callback Reference
| Callback | Parameters | When Fired |
|---|---|---|
onReady | consent | Zest initialized |
onAccept | consent | User accepted (any categories) |
onReject | none | User rejected all |
onChange | consent | Any consent change |
onGeo | action, verdict | Geo resolution completed — only when geo is configured (v2.4.0+) |
Practical Examples
Google Analytics Integration
document.addEventListener('zest:consent', (e) => {
if (e.detail.consent.analytics) {
// Load gtag.js dynamically
const script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=GA-XXXXX';
script.async = true;
document.head.appendChild(script);
script.onload = () => {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA-XXXXX');
};
}
});Facebook Pixel Integration
document.addEventListener('zest:consent', (e) => {
if (e.detail.consent.marketing) {
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'PIXEL_ID');
fbq('track', 'PageView');
}
});Consent-Aware Analytics Wrapper
class ConsentAwareAnalytics {
constructor() {
this.queue = [];
this.initialized = false;
document.addEventListener('zest:consent', (e) => {
if (e.detail.consent.analytics) {
this.initialize();
}
});
// Check if already consented
if (typeof Zest !== 'undefined' && Zest.hasConsent('analytics')) {
this.initialize();
}
}
initialize() {
if (this.initialized) return;
this.initialized = true;
// Initialize your analytics here
// ...
// Process queued events
this.queue.forEach(event => this.track(event.name, event.data));
this.queue = [];
}
track(name, data) {
if (!this.initialized) {
this.queue.push({ name, data });
return;
}
// Send to analytics
gtag('event', name, data);
}
}
const analytics = new ConsentAwareAnalytics();
// Safe to call anytime - will queue if no consent
analytics.track('button_click', { label: 'signup' });Show Banner on Footer Link Click
<footer>
<a href="#" onclick="Zest.showSettings(); return false;">
Cookie Preferences
</a>
</footer>Log Consent for Compliance
document.addEventListener('zest:consent', (e) => {
const proof = Zest.getConsentProof();
// Send to your server
fetch('/api/consent-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
consent: proof,
userAgent: navigator.userAgent,
url: window.location.href
})
});
}); 