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 | Cookies Used | Consent Needed |
|---|---|---|
| Privacy Mode | None | No |
| Standard Mode | Session cookie | May need |
| Extended Mode | Persistent cookie | Yes |
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 privacy mode (no cookies)
- Strictly necessary functionality
- Anonymous aggregate statistics
Privacy Mode (No Cookies)
Enable cookieless tracking by turning on Privacy Mode in the dashboard:
- Go to Settings → Privacy
- Toggle Privacy Mode ON
- Save changes
Your script tag stays the same:
<script
defer
data-tracking-code="YOUR_TRACKING_CODE"
src="https://api.zenovay.com/z.js"
></script>
Privacy mode:
- Uses no cookies
- No persistent identifiers
- GDPR/CCPA compliant without consent
- Slightly less accurate returning visitor data
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 privacy mode
loadZenovayPrivacyMode();
}
function loadZenovayPrivacyMode() {
// Privacy mode is configured in dashboard Settings → Privacy.
// When enabled there, the tracker automatically uses cookieless mode.
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);
}
</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 privacy mode
loadZenovayPrivacyMode();
}
}
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, enable Privacy Mode
// in dashboard Settings → Privacy instead.
}
}, [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;
loadZenovayPrivacyMode();
}
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 loadZenovayPrivacyMode() {
// Privacy mode is configured in dashboard Settings → Privacy.
// When enabled, the tracker automatically uses cookieless mode.
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);
}
</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