The short version
- In the EEA/UK, every ad tag that processes personal data needs a valid TCF v2.2 consent string.
-
The string is delivered through the
${GDPR_CONSENT_755}macro (GAM) or__tcfapi()(on-page JS). -
If
${GDPR}=1, the consent string must be non-empty and must decode to a valid TCF v2.2 payload. - Vendor IDs in the consent string must include the vendor you're calling, or the vendor should decline to serve.
-
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:
- Version: 2 (for TCF v2.0/2.1/2.2)
- Timestamps: created + last updated (deciseconds since epoch)
- CMP ID + version: which consent manager issued it
- Consent screen: which screen the user saw
- Consent language: ISO 639-1 code
- Vendor List Version: which GVL (Global Vendor List) was active
- Purpose consents / legitimate interests: bitfield of 11 purposes
- Vendor consents / legitimate interests: bitfield of all registered vendors
- Publisher restrictions: per-purpose, per-vendor overrides
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:
tcData.gdprApplies === truein EEA/UK placementstcData.tcStringis a non-empty, valid-format string-
tcData.cmpStatus === 'loaded'andtcData.eventStatus === 'tcloaded'or'useractioncomplete' -
tcData.purposeOneTreatment— some jurisdictions treat Purpose 1 differently
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:
- Position 1: Specification version (always
1) -
Position 2: Notice given (
Y/N/-) -
Position 3: Opted out of sale
(
Y/N/-) -
Position 4: LSPA signatory
(
Y/N/-)
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:
-
Checks for TCF v2.0/2.2 macros (
${GDPR},${GDPR_CONSENT_755}) — flag if GDPR-region tag is missing them -
Checks for US Privacy macro (
${US_PRIVACY}) — flag if US-targeting tag is missing - Checks for GPP macro (
${GPP}) — flag as a compatibility warning -
If the tag runs a
__tcfapicall, verifies it handles the async-callback pattern correctly - Produces a compliance score (0–100) based on macro presence, placement, and vendor-specific requirements
A minimum consent-validation QA pass
- Paste the tag into the validator. Confirm it flags all missing consent macros.
- Manually insert the correct macros using GAM syntax. Re-scan.
-
If the tag includes
__tcfapilogic, test it in a headless browser with a mock TCF response. Confirm the ad renders only whentcData.gdprApplies === falseOR when the required vendor ID is intcData.vendor.consents. -
Confirm
gdprandgdpr_consentmacros 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 →