Skip to main content

Menu

Choose a theme and configure high-contrast mode. Preferences are saved in your browser only.

User Preferences

Theme

Pick a palette or follow your system preference.

High Contrast

Sharper text and borders. System follows your OS setting.

Events & Callbacks

Overview

Zest provides three ways to react to consent changes:

  1. DOM Events - Standard browser events you can listen to anywhere
  2. Zest.on() / Zest.once() subscribers (v2.0.0+) - Cleaner API with explicit unsubscribe
  3. 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 a Zest surface is displayed.

document.addEventListener('zest:show', (e) => {
  console.log('UI shown:', e.detail.type);
  // 'banner' | 'modal' | 'notice'. 'notice' is the geo "Do Not Sell" link
});

zest:hide

Fired when a Zest surface is hidden. detail.type is 'banner' or 'modal'.

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'.

zest:embed

Since v3.0.0.

Fired when a third-party iframe is blocked or activated. In the headless build, use this event to render your own load control; no overlay is supplied. Loading one embed with Zest.activateEmbed() doesn’t record consent.

document.addEventListener('zest:embed', (e) => {
  const { phase, element, category, url } = e.detail;
  // phase: 'blocked' | 'activated'; element is the iframe
  console.log(phase, element, category, url);
});

Zest.on(Zest.EVENTS.EMBED, (e) => console.log(e.detail.phase));

DNT Auto-Reject Fires Your Listeners

With the default dntBehavior: 'reject', a visitor sending DNT or GPC is auto-rejected during initialization, before any UI appears. That reject is a real consent change: zest:reject, zest:change, and the onReject / onChange callbacks all fire, exactly as if the visitor had clicked Reject. If your analytics code initializes off those events, expect it to run (and immediately see everything denied) for these visitors. See DNT, GPC and Geo for how the signal interacts with geo gating.

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 constants

Zest.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');
  }
});
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' });
<footer>
  <a href="#" onclick="Zest.showSettings(); return false;">
    Cookie Preferences
  </a>
</footer>
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
    })
  });
});