LW IT Solutions
« Blog Overview /Data Privacy / Reading a TC String: What Sits in...
Read this article in other languages:

Reading a TC String: What Sits in the 213 Header Bits

Reading a TC String: What Sits in the 213 Header Bits
Contents
  1. How the String Is Packed
  2. The 213 Bits of the Header
  3. Why One String Is Short and the Next Is Endless
  4. What TCF 2.2 Turned Into an Error
  5. The Segments After the Core
  6. What This Settles in Practice
  7. Questions and answers
  8. Sources

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.

The core segment of a TC string as a bit strip: version, two timestamps, CMP fields, language, vendor list version and policy version, followed by the special features, purposes and legitimate interest bit fields and the vendor section
The core segment, bit by bit: 213 bits of header, then purposes and vendors.

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.

Questions and answers

What needs watching when a TC string is decoded by hand, without a library?

Three places where ordinary tools stumble. The first is the alphabet: web-safe base64 uses a dash and an underscore where many standard decoders expect a plus and a slash. Both characters then have to be swapped first, and missing “=” padding added until the length is divisible by four.

The second is the unit. A base64 decoder returns bytes, but the fields of the string ignore byte boundaries: the version takes six bits, each of the two timestamps 36. It is therefore easier to translate each character directly into six bits by its position in the alphabet, because that is how the string is built; swapping and padding then no longer matter. The fields are then counted off in order along one long run of zeros and ones.

The third is the end of the header. 213 bits are 35.5 characters, so the header ends halfway through the 36th character, and that character’s last three bits already belong to the vendor part. Keeping only the first 35 characters loses the end of the header; taking 36 reads three bits too many. Dots also separate the segments and are not part of the base64, so each segment is decoded on its own.

What if a site has no euconsent-v2 cookie?

Then the string sits somewhere else or under another name. The framework does not prescribe where it is stored, and euconsent-v2 is merely the common name. The reliable route is the interface every TCF CMP has to provide in the browser: calling __tcfapi('getTCData', 2, d => console.log(d.tcString)) in the console returns the current string in the tcString field, wherever the CMP keeps it.

How can a truncated string be recognised before a segment becomes unreadable?

By arithmetic the string itself allows. After bit 213, the core segment first carries the highest vendor ID the section covers, then a flag stating whether a bit field or ranges follow. With a bit field, that fixes how many bits the section occupies: one per vendor up to that ID. If the rest of the string is shorter, something is missing. With ranges, a count states in advance how many entries follow, and walking through them shows whether the string ends before the last one.

Two limits are possible causes. Under RFC 6265, browsers must support at least 4096 bytes per cookie, counting name, value and attributes; servers and intermediaries also cap the size of request headers, in which all of a domain’s cookies travel together. A string that ends close to such a limit is suspect, even if the core segment still reads in full.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Write a comment

Observations from other implementations, objections and questions are welcome here.

The email address is not published. Required fields are marked with an asterisk.

Articles & categories

CCTV

Follow this category by RSS

Cloud & AI

All 17 articles in this category Follow this category by RSS

Data Privacy

All 19 articles in this category Follow this category by RSS

Digital Analytics

All 58 articles in this category Follow this category by RSS

Digital Marketing

All 37 articles in this category Follow this category by RSS

IT & Networks

All 19 articles in this category Follow this category by RSS

Music Production

All 17 articles in this category Follow this category by RSS

Raspberry PI

Follow this category by RSS

SaaS & Internet Earning

Follow this category by RSS

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

All 11 articles in this category Follow this category by RSS

WordPress Plugins & Tricks

All 14 articles in this category Follow this category by RSS