Learn how to implement cookie consent and integrate it with Zenovay analytics for privacy compliance.
Do You Need a Cookie Banner?
Zenovay Cookie Usage
| Mode | Client-side storage | Consent needed |
|---|---|---|
| Cookieless mode | None (in-memory, window-scoped IDs) | Generally no |
| Default mode | A single first-party cookie to recognize returning visitors | May 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.
When Consent is Required
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
Basic Consent Check
// 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();
Simple Consent Banner
<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>
Popular Consent Platforms
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:
- Add Zenovay to "Statistics" category
- Set cookie name:
zenovay_session - Set provider:
zenovay.com - Set type:
HTTP - Set expiry:
Sessionor1 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();
}
});
Google Consent Mode
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
Consent Context
// 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);
Consent Banner Component
// 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>
Consent Preferences Page
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>
Consent Logging
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
Banner Design
- 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
Banner Shows Repeatedly
Verify:
- localStorage not blocked
- Cookie not expiring immediately
- Same domain for all pages
Consent Platform Not Triggering
Ensure:
- Integration code correct
- Platform fully loaded before check
- Callback names match