Testes A/B de banners de consentimento para cookies
O desafio
Pretende testar diferentes designs ou mensagens dos banners de cookies para melhorar as taxas de consentimento, mantendo a conformidade com o RGPD.
O cenário
«Quero fazer um teste A/B ao nosso banner de cookies para ver se diferentes formulações afetam as taxas de aceitação. Mas estou preocupado com a conformidade com o RGPD se rastrear os utilizadores antes de eles darem o seu consentimento.»
Abordagem de testes A/B em conformidade
O princípio fundamental: pode testar banners de cookies SEM ferramentas de rastreamento, utilizando apenas cookies estritamente necessários.
Implementação
// Create variant assignment (strictly necessary cookie)
function assignVariant() {
let variant = getCookie('banner_test_variant');
if (!variant) {
// Randomly assign variant
variant = Math.random() < 0.5 ? 'A' : 'B';
// Store in strictly necessary cookie (doesn't require consent)
document.cookie = banner_test_variant=${variant}; path=/; max-age=2592000; SameSite=Strict;
}
return variant;
}
// Configuration for Variant A (Control)
const configA = {
"notice_banner_type": "simple",
"website_name": "My Website",
// ... other config
};
// Configuration for Variant B (Test)
const configB = {
"notice_banner_type": "headline",
"website_name": "My Site", // Shorter name
// ... other config
};
// Initialize based on variant
document.addEventListener('DOMContentLoaded', function () {
const variant = assignVariant();
const config = variant === 'A' ? configA : configB;
// Add callback to track results (only after consent)
config.callbacks = {
"i_agree_button_clicked": () => {
// User accepted - log this (now they've consented to tracking)
logConsentEvent(variant, 'accepted');
},
"i_decline_button_clicked": () => {
// User declined - still log the action
logConsentEvent(variant, 'declined');
}
};
cookieconsent.run(config);
});
function logConsentEvent(variant, action) {
// Send to your analytics (user has now consented)
if (typeof gtag !== 'undefined') {
gtag('event', 'cookie_consent', {
'variant': variant,
'action': action
});
}
// Or send to backend
fetch('/api/log-consent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
variant: variant,
action: action,
timestamp: new Date().toISOString()
})
});
}
function getCookie(name) {
const value = ; ${document.cookie};
const parts = value.split(; ${name}=);
if (parts.length === 2) return parts.pop().split(';').shift();
}
Variáveis de teste
Tipo de banner
// Variant A: Simple banner
"notice_banner_type": "simple"
// Variant B: Headline banner
"notice_banner_type": "headline"
Esquema de cores
// Variant A: Light
"palette": "light"
// Variant B: Dark
"palette": "dark"
Texto do botão (através de CSS personalizado)
// Add CSS to change button text
const style = document.createElement('style');
style.textContent = variant === 'A'
? '.cc-nb-okagree::before { content: "Accept All"; }'
: '.cc-nb-okagree::before { content: "I Agree"; }';
document.head.appendChild(style);
Medir impressões de consentimento
// Backend endpoint to analyze results
// GET /api/consent-analytics
{
"variant_A": {
"impressions": 1000,
"accepted": 650,
"declined": 250,
"no_action": 100,
"acceptance_rate": 0.65
},
"variant_B": {
"impressions": 1000,
"accepted": 720,
"declined": 200,
"no_action": 80,
"acceptance_rate": 0.72
}
}
Consentimento entre domínios
Problema
«Temos www.example.com para o nosso site principal e shop.example.com para a nossa loja. Os utilizadores recebem o banner de cookies em AMBOS os sites, mesmo depois de terem aceitado no primeiro. É irritante e estamos a perder conversões.»
Compreender os desafios entre domínios
Por predefinição, os cookies estão limitados ao domínio específico onde são definidos:
- Cookie definido em
www.example.com→ Não acessível emshop.example.com - Cookie definido em
shop.example.com→ Não acessível emwww.example.com
Solução: parâmetro cookie_domain
Utilize o parâmetro cookie_domain para partilhar o consentimento entre subdomínios:
cookieconsent.run({
"consent_type": "express",
"website_name": "My Website",
"website_privacy_policy_url": "https://www.example.com/privacy",
"cookie_domain": ".example.com", // Note the leading dot!
"page_load_consent_levels": ["strictly-necessary"]
});
Importante! O ponto inicial (.example.com) torna o cookie acessível a:
example.comwww.example.comshop.example.comblog.example.com- Qualquer subdomínio de
example.com
Um exemplo completo entre domínios
Em todos os domínios/subdomínios, utilize uma configuração idêntica:
<!-- On www.example.com -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "light",
"language": "en",
"website_name": "Example",
"website_privacy_policy_url": "https://www.example.com/privacy",
"open_preferences_center_selector": "#changePreferences",
"page_load_consent_levels": ["strictly-necessary"],
"cookie_domain": ".example.com", // Shared across all subdomains
"cookie_secure": true // Recommended for production
});
});
</script>
<!-- On shop.example.com - SAME configuration -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
// EXACT SAME CONFIG as main domain
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "light",
"language": "en",
"website_name": "Example",
"website_privacy_policy_url": "https://www.example.com/privacy",
"open_preferences_center_selector": "#changePreferences",
"page_load_consent_levels": ["strictly-necessary"],
"cookie_domain": ".example.com", // Same domain!
"cookie_secure": true
});
});
</script>
Teste a configuração entre domínios
- Visite
www.example.com - Aceite os cookies
- Abra DevTools → Aplicação → Cookies
- Verifique se o domínio do cookie mostra
.example.com(com ponto à esquerda) - Navegue até
shop.example.com - Verifique se o banner NÃO aparece (consentimento já dado)
Problemas comuns entre domínios
Problema 1: O banner continua a aparecer nos subdomínios
Problema
«Defini cookie_domain como '.example.com', mas o banner continua a aparecer em todos os subdomínios.»
Causa
O navegador está a bloquear cookies de terceiros.
Solução
Certifique-se de que cookie_secure é verdadeiro para sites HTTPS.
// Make sure cookie_secure is true for HTTPS sites
"cookie_secure": true,
// Also verify in DevTools that cookie is actually being set
// Check: Application → Cookies → Look for cookie_consent_level
// Domain should show: .example.com (not www.example.com)
Problema 2: Consentimento diferente em subdomínios diferentes
Problema:
O utilizador dá o seu consentimento no domínio principal, mas as preferências diferem no subdomínio.
Causa:
As configurações não são idênticas entre domínios.
Solução:
Utilize um ficheiro de configuração ou variável partilhada.
// shared-config.js (include on all domains)
const SHARED_COOKIE_CONFIG = {
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "light",
"language": "en",
"website_name": "Example",
"website_privacy_policy_url": "https://www.example.com/privacy",
"open_preferences_center_selector": "#changePreferences",
"page_load_consent_levels": ["strictly-necessary"],
"cookie_domain": ".example.com",
"cookie_secure": true
};
// Then on each domain:
cookieconsent.run(SHARED_COOKIE_CONFIG);
Multidomínio (TLDs diferentes)
Problema
Tem example.com E example.co.uk, ou seja, domínios completamente diferentes.
Solução
Os cookies NÃO PODEM ser partilhados entre diferentes domínios de nível superior devido à segurança do navegador.
Tem duas opções:
Opção 1: Aceitar vários banners.
- O utilizador vê o banner em cada domínio de nível superior
- O consentimento dado aplica-se individualmente a cada domínio, o consentimento não é partilhado
- Esta é provavelmente a abordagem mais conforme
Opção 2: Utilizar uma sincronização de backend (para utilizadores com sessão iniciada)
// On example.com - user accepts
"callbacks": {
"user_consent_saved": () => {
if (isUserLoggedIn()) {
// Sync to backend
fetch('https://api.example.com/sync-consent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + getUserToken()
},
body: JSON.stringify({
consent: getConsentState(),
domains: ['example.com', 'example.co.uk']
})
});
}
}
}
// On example.co.uk - check backend first
async function checkBackendConsent() {
if (isUserLoggedIn()) {
const response = await fetch('https://api.example.com/get-consent', {
headers: {'Authorization': 'Bearer ' + getUserToken()}
});
const data = await response.json();
if (data.hasConsent) {
// Set cookie locally
setCookieConsent(data.consentLevel);
}
}
}
Recarregamento da página após o consentimento
Problema
«Depois de o utilizador aceitar os cookies, recarreguei a página com window.location.reload() para garantir que os scripts de rastreamento são carregados. Mas isto faz com que o Google Analytics perca a fonte de referência original e mostre todo o tráfego como "Direto".»
Isto acontece porque, quando recarrega a página:
- O navegador limpa as informações de referência
- O carregamento da nova página parece provir do mesmo domínio
- O Google Analytics classifica o tráfego como "Direto"
- Perde-se os dados de atribuição
Solução 1: Não recarregue (preferível)
Utilize callbacks para carregar scripts dinamicamente em vez de recarregar:
cookieconsent.run({
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "light",
"language": "en",
"website_name": "My Site",
"website_privacy_policy_url": "https://mysite.com/privacy",
"page_load_consent_levels": ["strictly-necessary"],
"callbacks": {
"scripts_specific_loaded": (level) => {
// Scripts are NOW loaded - no reload needed!
console.log("Scripts loaded for: " + level);
if (level === 'tracking') {
// Analytics is now active
// Send initial page view if needed
if (typeof gtag !== 'undefined') {
gtag('event', 'page_view', {
'page_location': window.location.href,
'page_referrer': document.referrer
});
}
}
}
},
"callbacks_force": true
});
Solução 2: Preservar o referenciador ao recarregar a página
Se tiver de recarregar, preserve o referenciador:
cookieconsent.run({
// ... config ...
"callbacks": {
"i_agree_button_clicked": () => {
// Store referrer and source before reload
sessionStorage.setItem('original_referrer', document.referrer);
sessionStorage.setItem('landing_page', window.location.href);
// Get URL parameters (utm_source, etc.)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('utm_source')) {
sessionStorage.setItem('utm_source', urlParams.get('utm_source'));
sessionStorage.setItem('utm_medium', urlParams.get('utm_medium'));
sessionStorage.setItem('utm_campaign', urlParams.get('utm_campaign'));
}
// Now reload
window.location.reload();
}
}
});
// After reload, restore attribution
document.addEventListener('DOMContentLoaded', function() {
const savedReferrer = sessionStorage.getItem('original_referrer');
const utmSource = sessionStorage.getItem('utm_source');
if (savedReferrer && typeof gtag !== 'undefined') {
// Send page view with preserved referrer
gtag('event', 'page_view', {
'page_location': window.location.href,
'page_referrer': savedReferrer
});
// Send campaign data if available
if (utmSource) {
gtag('event', 'campaign_details', {
'campaign_source': utmSource,
'campaign_medium': sessionStorage.getItem('utm_medium'),
'campaign_name': sessionStorage.getItem('utm_campaign')
});
}
// Clear storage
sessionStorage.removeItem('original_referrer');
sessionStorage.removeItem('utm_source');
// ... clear others
}
});
Solução 3: Utilize o Modo de Consentimento do Google (prática recomendada)
Com o Modo de Consentimento V2, NÃO é necessário recarregar a página:
<!-- 1. Set default consent to denied -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied'
});
</script>
<!-- 2. Load Google Analytics (runs in consent mode) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<!-- 3. Cookie Consent with callbacks -->
<script src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
"consent_type": "express",
"website_name": "My Site",
"page_load_consent_levels": ["strictly-necessary"],
"callbacks": {
"scripts_specific_loaded": (level) => {
// Update consent mode - NO RELOAD!
switch (level) {
case 'tracking':
gtag('consent', 'update', {
'analytics_storage': 'granted'
});
break;
case 'targeting':
gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted'
});
break;
}
}
},
"callbacks_force": true
});
});
</script>
Conteúdo incorporado e iFrames
Problema
«Temos vídeos do YouTube incorporados no nosso site. Devemos bloquear os iframes até que os utilizadores aceitem os cookies? Como implementamos isto com o TermsFeed?»
Solução
A solução consiste em integrar um espaço reservado e utilizar o carregamento dinâmico.
Passo 1: Criar HTML de espaço reservado
<!-- Instead of directly embedding iframe -->
<div class="video-placeholder" data-video-id="dQw4w9WgXcQ" data-consent-type="targeting">
<div class="placeholder-content">
<img src="/images/video-thumbnail.jpg" alt="Video thumbnail">
<div class="placeholder-overlay">
<button class="accept-and-load" onclick="loadVideo(this)">
Accept cookies to watch this video
</button>
<p>This video is hosted by YouTube and requires marketing cookies.</p>
<a href="#" onclick="openCookiePreferences(); return false;">
Change cookie preferences
</a>
</div>
</div>
</div>
<style>
.video-placeholder {
position: relative;
width: 100%;
padding-bottom: 56.25%; // 16:9 aspect ratio
background: #000;
}
.placeholder-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.placeholder-content img {
width: 100%;
height: 100%;
object-fit: cover;
}
.placeholder-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
text-align: center;
padding: 20px;
}
.accept-and-load {
background: #ff0000;
color: white;
border: none;
padding: 15px 30px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
margin-bottom: 15px;
}
</style>
Passo 2: Carregar o iFrame após o consentimento
// Check if user has already consented
function hasConsent(consentType) {
const consentCookie = getCookie('cookie_consent_level');
if (!consentCookie) return false;
// Parse consent levels
const levels = JSON.parse(decodeURIComponent(consentCookie));
return levels.includes(consentType);
}
// Load video dynamically
function loadVideo(button) {
const placeholder = button.closest('.video-placeholder');
const videoId = placeholder.dataset.videoId;
const consentType = placeholder.dataset.consentType;
// Check if user has consent
if (!hasConsent(consentType)) {
// Request consent
if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
cookieconsent.openPreferencesCenter();
} else {
alert('Please accept marketing cookies to view this content');
}
return;
}
// Create iframe
const iframe = document.createElement('iframe');
iframe.src = https://www.youtube.com/embed/${videoId};
iframe.width = '100%';
iframe.height = '100%';
iframe.frameBorder = '0';
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
iframe.allowFullscreen = true;
// Replace placeholder with iframe
placeholder.innerHTML = '';
placeholder.appendChild(iframe);
}
// Auto-load videos if consent already given
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.video-placeholder').forEach(placeholder => {
const consentType = placeholder.dataset.consentType;
if (hasConsent(consentType)) {
// Auto-load - user already consented
const videoId = placeholder.dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = https://www.youtube.com/embed/${videoId};
iframe.width = '100%';
iframe.height = '100%';
iframe.frameBorder = '0';
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
iframe.allowFullscreen = true;
placeholder.innerHTML = '';
placeholder.appendChild(iframe);
}
});
});
// Listen for consent updates
document.addEventListener('DOMContentLoaded', function() {
cookieconsent.run({
// ... config ...
"callbacks": {
"scripts_specific_loaded": (level) => {
// When user accepts targeting cookies, load all pending videos
if (level === 'targeting') {
document.querySelectorAll('.video-placeholder').forEach(placeholder => {
if (placeholder.dataset.consentType === 'targeting') {
loadVideo(placeholder.querySelector('.accept-and-load'));
}
});
}
}
}
});
});
function getCookie(name) {
const value = ; ${document.cookie};
const parts = value.split(; ${name}=);
if (parts.length === 2) return parts.pop().split(';').shift();
}
function openCookiePreferences() {
if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
cookieconsent.openPreferencesCenter();
}
}
Exemplo para outras incorporações
Google Maps
<div class="map-placeholder" data-consent-type="functionality" data-location="New York, NY">
<div class="placeholder-overlay">
<p>️ Accept functional cookies to view map</p>
<button onclick="loadMap(this)">Load Map</button>
</div>
</div>
<script>
function loadMap(button) {
const placeholder = button.closest('.map-placeholder');
const location = placeholder.dataset.location;
if (!hasConsent('functionality')) {
cookieconsent.openPreferencesCenter();
return;
}
const iframe = document.createElement('iframe');
iframe.src = https://www.google.com/maps/embed/v1/place?key=YOUR_API_KEY&q=${encodeURIComponent(location)};
iframe.width = '100%';
iframe.height = '450';
iframe.style.border = '0';
iframe.allowFullscreen = true;
iframe.loading = 'lazy';
placeholder.innerHTML = '';
placeholder.appendChild(iframe);
}
</script>
Incorporações de redes sociais (X, Instagram, Facebook)
<div class="social-placeholder" data-consent-type="targeting" data-platform="twitter" data-url="https://twitter.com/user/status/123">
<div class="placeholder-overlay">
<p> Accept marketing cookies to view this tweet</p>
<button onclick="loadSocialEmbed(this)">Load Tweet</button>
</div>
</div>
<script>
function loadSocialEmbed(button) {
const placeholder = button.closest('.social-placeholder');
const platform = placeholder.dataset.platform;
const url = placeholder.dataset.url;
if (!hasConsent('targeting')) {
cookieconsent.openPreferencesCenter();
return;
}
// Load platform SDK
if (platform === 'twitter' && !window.twttr) {
const script = document.createElement('script');
script.src = 'https://platform.twitter.com/widgets.js';
script.onload = () => {
twttr.widgets.createTweet(
url.split('/').pop(),
placeholder,
{ theme: 'light' }
);
};
document.head.appendChild(script);
}
// Add other platforms as needed
}
</script>