Skip to main content
Free20 minutesIntermediate

Cookie Consent Implementation

Implement cookie consent banners and integrate with Zenovay for compliant analytics tracking. Learn about cookies in this privacy compliance guide.

cookiesconsentgdprbannercompliance
Last updated:

Learn how to implement cookie consent and integrate it with Zenovay analytics for privacy compliance.

ModeClient-side storageConsent needed
Cookieless modeNone (in-memory, window-scoped IDs)Generally no
Default modeA single first-party cookie to recognize returning visitorsMay need (disclose in your privacy policy)

Zenovay is always cookieless on the server side: no IP address or personal data is stored. The table above is only about what the tracking script writes to the visitor's browser. Cookieless mode prevents the script from writing any cookie or localStorage entry at all.

Consent typically required when:

  • Using persistent cookies
  • Tracking across sessions
  • Identifying returning visitors
  • Collecting personal data

Consent may not be required when:

  • Using cookieless mode (no cookies)
  • Strictly necessary functionality
  • Anonymous aggregate statistics

Cookieless Mode (No Cookies)

You can run the Zenovay tracker in cookieless mode, which writes nothing to the visitor's browser. There are two ways to enable it.

Option 1: script attribute. Add data-cookieless="true" to your script tag:

<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  data-cookieless="true"
  src="https://api.zenovay.com/z.js"
></script>

Option 2: website setting. Open your website's dashboard, go to its Settings, and turn on the cookieless toggle (it appears in both the General and Advanced tabs). The General tab also regenerates the snippet above with data-cookieless="true" already added.

Cookieless mode:

  • Uses no cookies and no localStorage (in-memory, window-scoped IDs only)
  • Creates no persistent identifiers
  • Lawful pre-consent under ePrivacy for the Zenovay tracker itself
  • Slightly less accurate returning-visitor data (expect ~10-15% fewer uniques)

Other tools you load (Meta Pixel, Google Analytics, and similar) still set their own cookies and may require consent regardless of this setting.

Conditional Loading

// Check consent before loading
function loadZenovayIfConsented() {
  const consent = localStorage.getItem('analytics_consent');

  if (consent === 'granted') {
    loadZenovay();
  } else if (consent === 'denied') {
    // Don't load analytics
  } else {
    showConsentBanner();
  }
}

function loadZenovay() {
  const script = document.createElement('script');
  script.src = 'https://api.zenovay.com/z.js';
  script.setAttribute('data-tracking-code', 'YOUR_TRACKING_CODE');
  script.defer = true;
  document.head.appendChild(script);
}

// Run on page load
loadZenovayIfConsented();
<div id="cookie-banner" style="display: none;">
  <div class="cookie-content">
    <p>We use analytics to improve your experience.</p>
    <button onclick="acceptCookies()">Accept</button>
    <button onclick="declineCookies()">Decline</button>
    <a href="/privacy">Learn more</a>
  </div>
</div>

<script>
function showConsentBanner() {
  document.getElementById('cookie-banner').style.display = 'block';
}

function acceptCookies() {
  localStorage.setItem('analytics_consent', 'granted');
  document.getElementById('cookie-banner').style.display = 'none';
  loadZenovay();
}

function declineCookies() {
  localStorage.setItem('analytics_consent', 'denied');
  document.getElementById('cookie-banner').style.display = 'none';
  // Optionally load in cookieless mode (no browser storage)
  loadZenovayCookieless();
}

function loadZenovayCookieless() {
  // data-cookieless="true" tells the tracker to use in-memory,
  // window-scoped IDs only - no cookies, no localStorage.
  const script = document.createElement('script');
  script.src = 'https://api.zenovay.com/z.js';
  script.setAttribute('data-tracking-code', 'YOUR_TRACKING_CODE');
  script.setAttribute('data-cookieless', 'true');
  script.defer = true;
  document.head.appendChild(script);
}
</script>

Cookiebot Integration

// Wait for Cookiebot consent
window.addEventListener('CookiebotOnAccept', function() {
  if (Cookiebot.consent.statistics) {
    loadZenovay();
  }
});

// Handle consent changes
window.addEventListener('CookiebotOnDecline', function() {
  // Consent withdrawn - stop tracking
  if (window.zenovay) {
    window.zenovay('disable');
  }
});

Cookiebot Configuration:

  1. Add Zenovay to "Statistics" category
  2. Set cookie name: zenovay_session
  3. Set provider: zenovay.com
  4. Set type: HTTP
  5. Set expiry: Session or 1 year

OneTrust Integration

// OneTrust consent callback
function OptanonWrapper() {
  if (OnetrustActiveGroups.includes('C0002')) {
    // Performance cookies consented
    loadZenovay();
  } else {
    // Load in cookieless mode
    loadZenovayCookieless();
  }
}

OneTrust Categories:

  • C0001: Strictly Necessary
  • C0002: Performance (Zenovay)
  • C0003: Functional
  • C0004: Targeting

Osano Integration

// Osano consent callback
window.Osano.cm.addEventListener('osano-cm-consent-changed', function(event) {
  if (event.ANALYTICS === 'ACCEPT') {
    loadZenovay();
  }
});

Termly Integration

// Termly consent callback
window.addEventListener('termly-consent-preferences-updated', function(e) {
  if (e.detail.analytics) {
    loadZenovay();
  }
});

Integration

// Initialize Google Consent Mode
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}

gtag('consent', 'default', {
  'analytics_storage': 'denied'
});

// Update on consent
function updateConsent(granted) {
  gtag('consent', 'update', {
    'analytics_storage': granted ? 'granted' : 'denied'
  });

  if (granted) {
    loadZenovay();
  }
}

With Zenovay

Use the conditional loading approach shown above to only load Zenovay after consent is granted through Google Consent Mode:

// Load Zenovay only after analytics consent is granted
function updateConsent(granted) {
  gtag('consent', 'update', {
    'analytics_storage': granted ? 'granted' : 'denied'
  });

  if (granted) {
    const script = document.createElement('script');
    script.src = 'https://api.zenovay.com/z.js';
    script.setAttribute('data-tracking-code', 'YOUR_TRACKING_CODE');
    script.defer = true;
    document.head.appendChild(script);
  }
}

React Implementation

// ConsentContext.js
import { createContext, useContext, useState, useEffect } from 'react';

const ConsentContext = createContext();

export function ConsentProvider({ children }) {
  const [consent, setConsent] = useState(() => {
    return localStorage.getItem('analytics_consent') || 'pending';
  });

  const grantConsent = () => {
    localStorage.setItem('analytics_consent', 'granted');
    setConsent('granted');
  };

  const denyConsent = () => {
    localStorage.setItem('analytics_consent', 'denied');
    setConsent('denied');
  };

  return (
    <ConsentContext.Provider value={{ consent, grantConsent, denyConsent }}>
      {children}
    </ConsentContext.Provider>
  );
}

export const useConsent = () => useContext(ConsentContext);
// CookieBanner.js
import { useConsent } from './ConsentContext';

export function CookieBanner() {
  const { consent, grantConsent, denyConsent } = useConsent();

  if (consent !== 'pending') return null;

  return (
    <div className="cookie-banner">
      <p>We use cookies to analyze website traffic.</p>
      <button onClick={grantConsent}>Accept</button>
      <button onClick={denyConsent}>Decline</button>
    </div>
  );
}

Conditional Analytics

// Analytics.js
import { useEffect } from 'react';
import { useConsent } from './ConsentContext';

export function Analytics({ websiteId }) {
  const { consent } = useConsent();

  useEffect(() => {
    if (consent === 'granted') {
      const script = document.createElement('script');
      script.src = 'https://api.zenovay.com/z.js';
      script.setAttribute('data-tracking-code', websiteId);
      script.defer = true;
      document.head.appendChild(script);
    } else if (consent === 'denied') {
      // Don't load analytics when consent denied.
      // If you still want minimal tracking, load the script with
      // data-cookieless="true" instead (no cookies, no localStorage).
    }
  }, [consent, websiteId]);

  return null;
}

Vue.js Implementation

<!-- CookieConsent.vue -->
<template>
  <div v-if="showBanner" class="cookie-banner">
    <p>We use cookies for analytics.</p>
    <button @click="accept">Accept</button>
    <button @click="decline">Decline</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const showBanner = ref(false);

onMounted(() => {
  const consent = localStorage.getItem('analytics_consent');
  if (!consent) {
    showBanner.value = true;
  } else if (consent === 'granted') {
    loadZenovay();
  }
});

function accept() {
  localStorage.setItem('analytics_consent', 'granted');
  showBanner.value = false;
  loadZenovay();
}

function decline() {
  localStorage.setItem('analytics_consent', 'denied');
  showBanner.value = false;
  loadZenovayCookieless();
}

function loadZenovay() {
  const script = document.createElement('script');
  script.src = 'https://api.zenovay.com/z.js';
  script.setAttribute('data-tracking-code', 'YOUR_TRACKING_CODE');
  document.head.appendChild(script);
}

function loadZenovayCookieless() {
  // data-cookieless="true" makes the tracker use in-memory,
  // window-scoped IDs only - no cookies, no localStorage.
  const script = document.createElement('script');
  script.src = 'https://api.zenovay.com/z.js';
  script.setAttribute('data-tracking-code', 'YOUR_TRACKING_CODE');
  script.setAttribute('data-cookieless', 'true');
  document.head.appendChild(script);
}
</script>

Allow users to change preferences:

<div class="cookie-preferences">
  <h2>Cookie Preferences</h2>

  <div class="cookie-option">
    <label>
      <input type="checkbox" id="necessary" checked disabled>
      Necessary Cookies (Required)
    </label>
    <p>Essential for the website to function.</p>
  </div>

  <div class="cookie-option">
    <label>
      <input type="checkbox" id="analytics">
      Analytics Cookies
    </label>
    <p>Help us understand how visitors use our site.</p>
  </div>

  <button onclick="savePreferences()">Save Preferences</button>
</div>

<script>
// Load current preferences
document.getElementById('analytics').checked =
  localStorage.getItem('analytics_consent') === 'granted';

function savePreferences() {
  const analytics = document.getElementById('analytics').checked;
  localStorage.setItem('analytics_consent', analytics ? 'granted' : 'denied');

  // Reload to apply changes
  location.reload();
}
</script>

Track consent for compliance:

function logConsent(type, granted) {
  fetch('/api/consent-log', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: type,
      granted: granted,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent
    })
  });
}

function acceptCookies() {
  logConsent('analytics', true);
  localStorage.setItem('analytics_consent', 'granted');
  loadZenovay();
}

Best Practices

  • Clear language: Explain what cookies do
  • Equal options: Accept and decline equally prominent
  • No dark patterns: Don't trick users into accepting
  • Granular control: Let users choose categories
  • Easy withdrawal: Allow changing preferences

Technical

  • Don't load before consent: Only load tracking after consent
  • Respect withdrawal: Stop tracking when consent withdrawn
  • Remember preferences: Don't ask repeatedly
  • Test thoroughly: Verify consent flow works

Compliance

  • Document consent: Log when consent given/withdrawn
  • Regular review: Update as regulations change
  • Audit trail: Keep records for compliance

Troubleshooting

Tracking Still Active After Decline

Check:

  • Script not hardcoded in HTML
  • Consent check runs before any tracking
  • No caching issues

Verify:

  • localStorage not blocked
  • Cookie not expiring immediately
  • Same domain for all pages

Ensure:

  • Integration code correct
  • Platform fully loaded before check
  • Callback names match

Next Steps

Was this article helpful?