Reading a TC String: What Sits in the 213 Header Bits
Contents
Arguments about consent usually run on assertions: the banner was configured this way, the vendor list is current, the string is fresh. All three are written down, in a cookie called euconsent-v2, and none of them needs to be argued about – because the string is not encrypted. It is packed, and unpacking it takes no key.
What follows is the layout of that packing. It is worth knowing not for its own sake but because reading a string settles in half a minute what otherwise turns into a discussion with a vendor: whether the CMP wrote what the dialogue promised, when it last did so, and against which version of the vendor list.

How the String Is Packed
A TC string is a sequence of fields of fixed bit length, written one after another without separators, and then encoded in web-safe base64 – the variant that uses a dash and an underscore instead of the two characters that would need escaping in a URL. Each character carries exactly six bits.
That is why every TC string in the wild starts with the letter C: the first field is the version, six bits wide, and version 2 written in six bits is 000010, which is the character at position 2 in the alphabet. A string that starts with anything else is either not a TC string or not version 2, and both are worth noticing before anything else is read.
The 213 Bits of the Header
Everything up to the vendors sits in a fixed layout, which is what makes it readable without a library.
| Field | Bits | What it settles |
|---|---|---|
| Version | 6 | Always 2 in practice; anything else does not belong to this format. |
| Created, LastUpdated | 36 each | Tenths of a second since 1970 – the age of the consent, to the second. |
| CmpId, CmpVersion | 12 each | Which CMP wrote the string, in which build. |
| VendorListVersion | 12 | The version of the global vendor list the dialogue was built from. |
| TcfPolicyVersion | 6 | 4 and above means the string follows the TCF 2.2 rules. |
| SpecialFeatureOptIns | 12 | Precise location and device scanning, as two of twelve possible bits. |
| PurposesConsent, PurposesLI | 24 each | Eleven defined purposes, twice: consent and legitimate interest. |
Together with the consent screen number, the two-letter language, the service-specific flag and the publisher country, that comes to 213 bits. From bit 214 onwards the vendors begin, and there the length stops being fixed.
Why One String Is Short and the Next Is Endless
Vendors are stored in one of two ways, and the string chooses per section. Either as a bit field, one bit per vendor up to the highest identifier – which costs 1200 bits when the list runs that far, regardless of how many vendors were actually agreed to. Or as ranges: a count, then a start and an end per range, sixteen bits each.
A visitor who agrees to everything produces a compact string, because the whole list collapses into a handful of ranges. A visitor who picks fifty vendors out of eight hundred produces the long one, because every isolated choice becomes its own range. The length of the cookie therefore says something about the shape of the decision, and nothing at all about how permissive it was.
What TCF 2.2 Turned Into an Error
Three things are worth checking in every string, and all three became firm rules rather than opinions with policy version 4.
Legitimate interest is no longer permitted for purposes 1, 3, 4, 5 and 6. A string that carries those bits was either produced under the old policy or written by a CMP that has not followed the change – and the bit is not harmless, because vendors read it as a basis for processing.
Purposes 12 to 24 exist in the format but not in the specification. Bits set there mean a CMP is writing into fields that no one defines, which is the clearest sign of a hand-rolled implementation.
And the age: the update timestamp is in the string, so a consent older than thirteen months can be recognised from the cookie alone. Together with the vendor list version, that answers the question of whether a dialogue is showing vendors the visitor has never been asked about – because the list grows, and a string written against an older version simply has nothing to say about the newcomers.
The Segments After the Core
A string can carry more than one segment, separated by dots, and each additional one begins with three bits naming its type. Type 1 lists the vendors that were disclosed on the device, type 3 carries the publisher’s own purposes – the ones a site may add for itself. Type 2 was the allowed-vendors segment and is gone in 2.2.
Anything past the first dot is therefore optional, and its absence is not a fault. What is a fault is a second segment that no one can decode because a proxy truncated the cookie at a length limit, which is the usual explanation for a string that reads correctly up to a point and then stops.
What This Settles in Practice
Three questions come up in almost every consent review, and the string answers all of them without anyone’s cooperation. Whether the CMP wrote what the dialogue promised – by comparing the purpose bits against the choices made. Whether the setup is current – by the policy version and the vendor list version. And whether a visitor is being carried on a decision from another era – by the update timestamp.
None of that replaces a look at the dialogue itself, and the string says nothing about whether a vendor honours it. But it turns three assertions into three readings, and that is usually enough to end the part of the discussion that was never about facts.