Published 2026-04-18 · 9 min read · GDPR / TCF / Consent

TCF v2.2 and GDPR Consent Validation for Ad Tags

Consent signals are the least-tested part of most ad tags. If GDPR applies and your tag doesn't carry a valid TCF string, 30–50% of vendors will refuse to serve or will serve without matching purpose/vendor IDs. Here's how to verify it's working before you ship.

The short version

  1. In the EEA/UK, every ad tag that processes personal data needs a valid TCF v2.2 consent string.
  2. The string is delivered through the ${GDPR_CONSENT_755} macro (GAM) or __tcfapi() (on-page JS).
  3. If ${GDPR}=1, the consent string must be non-empty and must decode to a valid TCF v2.2 payload.
  4. Vendor IDs in the consent string must include the vendor you're calling, or the vendor should decline to serve.
  5. For US traffic, you additionally need ${US_PRIVACY} (older CCPA/USP format) or ${GPP} (newer, multi-jurisdiction).

Anatomy of a TCF v2.2 consent string

A consent string is a base64-url-encoded binary payload. Example truncated for readability:

CPx5qwAPx5qwAAKAAAENAfCAAAAAAAAAAAAAAAAAAAAA.IFoAAAAAAAAAAAAAA

It encodes:

A decoded string tells you exactly: who consented, to what, for whom, when, and through which CMP. If any field is corrupt or missing, the whole string is invalid.

Rule of thumb: if the consent string is shorter than 30 characters, it's almost certainly invalid. Valid TCF v2.2 strings are typically 80–300+ characters.

The CMP JS API

On-page (non-ad-server) code talks to the CMP via window.__tcfapi():

window.__tcfapi('getTCData', 2, function(tcData, success) {
  if (!success) return; // CMP not ready
  console.log('TCF version:', tcData.tcfPolicyVersion);
  console.log('GDPR applies:', tcData.gdprApplies);
  console.log('Consent string:', tcData.tcString);
  console.log('Purposes:', tcData.purpose.consents);
  console.log('Vendor consents:', tcData.vendor.consents);
});

What to check:

GDPR macros by vendor

Vendor GDPR-applies macro Consent string macro
GAM (universal) ${GDPR} ${GDPR_CONSENT_755}
Flashtalking ftXGdpr=${GDPR} ftXGdprConsent=${GDPR_CONSENT_755}
Xandr gdpr=${GDPR} gdpr_consent=${GDPR_CONSENT_755}
Criteo gdpr=${GDPR} gdpr_consent=${GDPR_CONSENT_755}
DV360 gdpr=${GDPR} gdpr_consent=${GDPR_CONSENT_755}
Amazon DSP gdpr=${GDPR} gdpr_consent=${GDPR_CONSENT_755}
Innovid gdpr=${GDPR} gdpr_consent=${GDPR_CONSENT_755}
Taboola (from US Privacy signal) gdpr_consent=${GDPR_CONSENT_755}

The 755 in ${GDPR_CONSENT_755} is Google's GVL vendor ID. This macro tells GAM to inject the TCF consent string into the tag at ad-call time.

The "both or neither" rule

If you pass gdpr=1 you must also pass a non-empty consent string. Several vendors (Criteo, Xandr, DV360) will refuse to serve if gdpr=1 arrives without a consent string, even if the user has consented through the CMP. The macros must always travel as a pair.

CreativeValidator decodes consent strings, checks the TCF version, and verifies the vendor is included in the consent payload — for every tag you scan.

Run a consent-aware scan →

US Privacy (CCPA / USP)

US Privacy uses a 4-character string:

Example: 1YNN — spec v1, notice given, user did not opt out, not LSPA.

The macro is ${US_PRIVACY}. If the string is 1-N-, the ad call should skip vendors that require opt-in.

GPP (Global Privacy Platform)

GPP replaces both TCF and USP long-term. A GPP string is a container that can hold multiple section strings — TCF EU, TCF Canada, US National, California, Virginia, Colorado, Connecticut, and so on.

// Check GPP signal
window.__gpp('ping', function(data) {
  console.log('GPP version:', data.gppVersion);
  console.log('Applicable sections:', data.applicableSections);
  console.log('GPP string:', data.gppString);
});

The GAM macro is ${GPP}. As of 2026, GPP adoption is partial — most major SSPs accept it, but many vendors still rely on the legacy TCF/USP macros. Pass all three (${GDPR_CONSENT_755}, ${US_PRIVACY}, ${GPP}) for maximum compatibility.

What actually breaks when consent is missing

Case 1: No consent macro on EEA traffic

Tag fires. Vendor sees gdpr=1 (GAM injects it automatically based on IP) but no consent string. Vendor returns a passback ad or blank. Fill rate drops 40%+.

Case 2: Consent string present, vendor not in consent

User consented to 3 of 11 purposes but the vendor needed all 11. Vendor returns a contextual-only ad (no retargeting, no behavioral). CPMs drop 60–80%.

Case 3: Consent string expired

TCF strings have a lastUpdatedAt timestamp. If older than 13 months, major vendors treat the user as un-consented. Most CMPs auto-renew, but stale strings do show up in the wild.

Case 4: Legitimate interest vs consent confusion

Some purposes (measurement, personalization) can be processed under legitimate interest rather than explicit consent. Vendors check vendor.legitimateInterests as well as vendor.consents. If your tag only checks consent and ignores LI, you miss fill.

Case 5: CMP not loaded yet

Publisher pages sometimes fire ad calls before the CMP resolves. If the ad tag runs __tcfapi('getTCData', 2, ...) and the CMP isn't ready, the callback never fires. You need to listen for __tcfapi('addEventListener', 2, ...) and wait for tcloaded.

Our consent validation pipeline

For every tag scanned, CreativeValidator:

  1. Checks for TCF v2.0/2.2 macros (${GDPR}, ${GDPR_CONSENT_755}) — flag if GDPR-region tag is missing them
  2. Checks for US Privacy macro (${US_PRIVACY}) — flag if US-targeting tag is missing
  3. Checks for GPP macro (${GPP}) — flag as a compatibility warning
  4. If the tag runs a __tcfapi call, verifies it handles the async-callback pattern correctly
  5. Produces a compliance score (0–100) based on macro presence, placement, and vendor-specific requirements

A minimum consent-validation QA pass

  1. Paste the tag into the validator. Confirm it flags all missing consent macros.
  2. Manually insert the correct macros using GAM syntax. Re-scan.
  3. If the tag includes __tcfapi logic, test it in a headless browser with a mock TCF response. Confirm the ad renders only when tcData.gdprApplies === false OR when the required vendor ID is in tcData.vendor.consents.
  4. Confirm gdpr and gdpr_consent macros travel together. A tag with one but not the other is worse than neither.

Consent-aware scanning for every ad tag, plus decoded TCF strings so you can see exactly what's being passed.

Validate consent handling →