pixellint

A TC String is not the letters 1 or true

Short answer

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.

Validate this snippet Open the rulepack

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. Artifacts you test may be stored; see privacy.