pixellint

Consent · engineers · PMs · marketers

A TC String is not the letters 1 or true

IAB TCF v2 puts a version in the first six bits. That version is 2. Base64 is a permissive alphabet, so placeholders sail through any check that only looks at characters. Pixellint core decodes those bits. It is not an IAB product.

gdpr is 0 or 1

gdpr is 0 or 1. Anything else, including true, yes, Y, and GDPR, is invalid. That is core.privacy.gdpr_invalid. The flag answers one question: does GDPR apply to this hop. It is not the user's yes. It is not a CMP dashboard screenshot. When the flag is missing, a populated gdpr_consent still leaves the callee guessing. That is core.privacy.gdpr_consent_without_flag.

When the flag is 0, a TC String on the same request is not meaningful for that hop. Core reports core.privacy.gdpr_consent_ignored. The string can be perfect. The flag said GDPR does not apply, so a v2 vendor is not supposed to treat the string as the basis for this call. Fix the flag or drop the string. Do not keep both as belt and suspenders.

https://example.com/px?id=1&gdpr=true&gdpr_consent=CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA

gdpr=1 needs a TC String

When the flag is 1, a TC String has to ride along in gdpr_consent. An empty gdpr_consent next to gdpr=1 is a missing signal, not a no from the user. Missing key and empty value are both core.privacy.gdpr_consent_missing. A fired Floodlight tag that still has gdpr=1;gdpr_consent= is the same miss with a different delimiter.

The string has to be the CMP output, unmodified. Do not replace it with 1 because a ticket said the user consented. Do not encode it twice. Do not concatenate vendor lists onto a stub. Consent on the wire is the decoded string, not the banner copy and not a boolean the ad server invented.

https://ad.doubleclick.net/ddm/activity/src=1234567;type=convr0;cat=purch0;ord=8675309;gdpr=1;gdpr_consent=CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA?

Decode, do not sniff the alphabet

A TC String is URL-safe base64. The characters 1 and true both live in that alphabet. gdpr_consent=1 and gdpr_consent=true pass any check that only looks at characters, and they still are not consent. Core still has to treat them as well-formed base64 segments, then decode. The first six bits have to read 2. If they read something else, core.privacy.tc_string_version fires. If the characters are not URL-safe base64 at all, core.privacy.gdpr_consent_malformed fires.

That is why a log that says consent present is not an audit. Open the value. If it is 1, true, yes, or a truncated stub, the vendor did not receive a TC String. pixellint validate url on the fired request is the check. A template that still holds [GDPR_CONSENT_123] is a different story, covered under macros.

https://example.com/px?id=1&gdpr=1&gdpr_consent=1

Version 2, 36 characters, sunset v1

The core segment has to hold the mandatory fields through PublisherCC. That is 213 bits, so 36 characters is the floor. Shorter is core.privacy.tc_string_truncated. Truncation usually comes from a macro length limit, a spreadsheet column, or a wrap that copies only the first chunk. A string that starts like a TC String and then stops is not a partial pass.

A TCF v1 string decodes to version 1. IAB Tech Lab sunset v1 on 15 August 2020. Pixellint reports that as sunset under core.privacy.tc_string_version, not as malformed, because it is a real string that no v2 vendor can read. Do not treat v1 as a character-set failure. Replace the CMP output with a v2 string. Optional disclosed and publisher segments after a dot are extra. The floor is about the core segment before the first dot.

https://example.com/px?id=1&gdpr=1&gdpr_consent=CPXxRfAPXxRf
https://example.com/px?id=1&gdpr=1&gdpr_consent=BOxxRfAOxxRfAAfKABENAAAAAAAAoAAA

Query string and Floodlight path

Pixellint core reads gdpr and gdpr_consent from the query string and from Floodlight-style path parameters. A semicolon tag on doubleclick.net is not exempt because it looks like a path. If the pair is on the artifact, it is on the artifact. The contract does not change when the delimiter does.

Some vendors also have consent fields on a CAPI JSON body. That is a different pack. Do not assume gdpr_consent on a pixel URL is copied into CAPI by magic. Do not assume Google Consent Mode granted ad_storage is a TC String. Floodlight still wants the IAB pair. gtag consent update does not write gdpr_consent for you.

Macros and empty slots

Ad servers expand [GDPR_CONSENT_...] at serve time. A fired Floodlight tag that still has the token never carried consent. Empty values and unexpanded macros are template exemptions. Core privacy rules stay quiet on those so a GAM creative is not punished for being a template. Macro rules then fail the fired state if the token is still there after serve.

A macro that expands to 1 or true is worse than one that does not expand. It looks populated in a log. It is still not a TC String. Confirm on a fired tag, not on the template. Template versus fired is the difference between a good GAM creative and a live miss.

https://ad.doubleclick.net/ddm/activity/src=1234567;type=conv0;cat=purch0;dc_rdid=;tfua=;npa=;gdpr=;gdpr_consent=;ord=8675309?

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.