Skip to content

Wiring with a CMP

Trackbridge gates click-identifier cookies on ad_storage consent (when consentMode: 'v2') but does not show a banner — that’s your CMP’s job. The integration is two callbacks: when the user grants consent, call tracker.updateConsent(...) with 'granted'; when they decline, call it with 'denied'.

This guide gives the wiring for the four common cases: Cookiebot, OneTrust, Iubenda, and rolling your own. For the underlying behavior, see Consent Mode v2.

Cookiebot exposes CookiebotOnAccept and CookiebotOnDecline events on window, plus a window.Cookiebot.consent object with per-category booleans:

import { tracker } from '@/lib/tracker.client';
window.addEventListener('CookiebotOnAccept', () => {
tracker.updateConsent({
ad_storage: window.Cookiebot.consent.marketing ? 'granted' : 'denied',
ad_user_data: window.Cookiebot.consent.marketing ? 'granted' : 'denied',
ad_personalization: window.Cookiebot.consent.marketing ? 'granted' : 'denied',
analytics_storage: window.Cookiebot.consent.statistics ? 'granted' : 'denied',
});
});
window.addEventListener('CookiebotOnDecline', () => {
tracker.updateConsent({
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
});
});

Cookiebot also fires CookiebotOnLoad when its prior-choice cookie restores consent on a return visit. Listen for it too if you want to replay the saved choice without the user re-clicking the banner.

OneTrust uses window.OptanonWrapper, called once after the SDK loads and again on every consent change. Group IDs are configured per project; the OneTrust defaults are C0001 (Strictly Necessary), C0002 (Performance), C0003 (Functional), C0004 (Targeting):

import { tracker } from '@/lib/tracker.client';
window.OptanonWrapper = function () {
const groups = window.OnetrustActiveGroups || '';
const has = (id: string) => groups.includes(`,${id},`);
tracker.updateConsent({
ad_storage: has('C0004') ? 'granted' : 'denied', // Targeting
ad_user_data: has('C0004') ? 'granted' : 'denied',
ad_personalization: has('C0004') ? 'granted' : 'denied',
analytics_storage: has('C0002') ? 'granted' : 'denied', // Performance
});
};

Replace C0002 / C0004 with your project’s actual group IDs. They’re in your OneTrust admin under Cookies → Categories.

Iubenda’s Consent Solution exposes a callback hook in the configuration object. The purpose IDs depend on your Iubenda project but 4 (Marketing) and 5 (Measurement) are the standards:

import { tracker } from '@/lib/tracker.client';
window._iub = window._iub || [];
window._iub.csConfiguration = window._iub.csConfiguration || {};
window._iub.csConfiguration.callback = {
...window._iub.csConfiguration.callback,
onConsentRead: function () {
const purposes = window._iub.cs.consent.purposes;
tracker.updateConsent({
ad_storage: purposes['4'] ? 'granted' : 'denied',
ad_user_data: purposes['4'] ? 'granted' : 'denied',
ad_personalization: purposes['4'] ? 'granted' : 'denied',
analytics_storage: purposes['5'] ? 'granted' : 'denied',
});
},
};

onConsentRead fires both on first interaction and on subsequent page loads when the saved choice is replayed — which is exactly the wiring you want.

If you’re rolling your own banner:

import { tracker } from '@/lib/tracker.client';
function onAcceptAll() {
tracker.updateConsent({
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
analytics_storage: 'granted',
});
saveConsentChoice('all');
}
function onAcceptEssentialOnly() {
tracker.updateConsent({
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
});
saveConsentChoice('essential');
}
function onAcceptCustom(choices: { marketing: boolean; analytics: boolean }) {
tracker.updateConsent({
ad_storage: choices.marketing ? 'granted' : 'denied',
ad_user_data: choices.marketing ? 'granted' : 'denied',
ad_personalization: choices.marketing ? 'granted' : 'denied',
analytics_storage: choices.analytics ? 'granted' : 'denied',
});
saveConsentChoice('custom', choices);
}

On every page load, replay the saved choice as soon as the tracker is created — don’t wait for the user to interact again:

const saved = readConsentChoice();
if (saved) {
tracker.updateConsent({
ad_storage: saved.marketing ? 'granted' : 'denied',
ad_user_data: saved.marketing ? 'granted' : 'denied',
ad_personalization: saved.marketing ? 'granted' : 'denied',
analytics_storage: saved.analytics ? 'granted' : 'denied',
});
}

Without the replay, every page load starts fresh and any captured gclid lives in memory only until consent is re-granted.

  1. Open the site in a fresh incognito window with DevTools → Application → Cookies open.
  2. Land on the site with ?gclid=test123 in the URL.
  3. Confirm no _tb_gclid cookie is present (consent is unknown).
  4. Click Accept on the banner.
  5. Confirm _tb_gclid now appears with value test123.
  6. Reload the page (drop the URL param). Confirm the cookie persists.
  7. Repeat from step 1, but click Decline. Confirm no cookie is written, even with ?gclid=test123 in the URL.

If the cookie doesn’t appear after Accept, the CMP callback isn’t reaching the tracker. Add a console.log('[cmp] updateConsent', granted) inside the callback to confirm it fires.

Trackbridge has its own consent state, separate from gtag’s. If you want gtag to also respect the user’s choice (which you usually do, for GA4 cookie behavior), call gtag’s consent API from the same CMP callback:

window.gtag('consent', 'update', {
ad_storage: choices.marketing ? 'granted' : 'denied',
ad_user_data: choices.marketing ? 'granted' : 'denied',
ad_personalization: choices.marketing ? 'granted' : 'denied',
analytics_storage: choices.analytics ? 'granted' : 'denied',
});

Trackbridge’s updateConsent does not call gtag('consent', ...) for you. That’s deliberate: not every project loads gtag, and projects that do may already wire gtag-consent through a separate path (Google Tag Manager, for instance).