webtracking.org
tools

Decode a TCF or GPP consent string: what the Consent / CMP Checker actually reads

Decode IAB TCF v2.2 consent strings and GPP headers into plain-English purposes and vendor consents, and read a page's live consent signals. 100% client-side.

last verified 2026-07-18

A consent string like CPcqCEA... is the densest artifact in web tracking: a base64url blob that encodes which processing purposes a user agreed to, which of several hundred ad-tech vendors got consent, which CMP recorded the choice, and when. It travels in cookies, ad requests, and the __tcfapi JavaScript API — and almost nobody implementing or auditing tags can read one by eye.

The free Consent / CMP Checker solves that in two directions. Paste a string and it decodes an IAB TCF v2.2 consent string or a GPP header into labelled fields. Or press one button and it reads the consent signals the current page is exposing to JavaScript: the TCF API, the GPP API, and Google Consent Mode state in dataLayer. Everything runs in your browser — the page makes no network requests and works offline.

It is built for two audiences: implementation engineers doing consent QA (“is the CMP writing what the banner claims?”) and privacy-minded practitioners who want to see what a recorded consent actually contains.

What it does

The tool has two tabs:

Decode a string. Paste a TC string (from the __tcfapi API, an ad request, or a cookie) or a GPP string. The type is auto-detected. For a TCF v2 string you get:

  • Header fields — TCF version, policy version, CMP ID, CMP version, consent screen, consent language, vendor list version, created / last-updated timestamps (converted to ISO dates), service-specific flag, purpose-one treatment, and publisher country.
  • Purposes consented — each purpose ID with its plain-English TCF v2.2 name, e.g. purpose 1 “Store and/or access information on a device” or purpose 7 “Measure advertising performance.” All 11 standard purposes are named.
  • Purposes under legitimate interest — the separate LI transparency list.
  • Special feature opt-ins — precise geolocation and active device scanning.
  • Vendor consents — the max vendor ID, whether the section is bitfield- or range-encoded, the total count of consented vendors, and the first 60 vendor IDs (with a “+N more” summary beyond that).

For a GPP string you get the header: GPP version, the list of section IDs present, and each ID mapped to its framework name — tcfeuv2, uspv1, usnat, usca, and the other US state sections through ustn. Per-section payload decoding is explicitly out of scope; the tool tells you so in the output.

This page. A live check of what the page you are on exposes to JavaScript right now: whether window.__tcfapi exists (and if so, its cmpStatus, eventStatus, gdprApplies, and a preview of the TC string, which the tool then decodes inline), whether window.__gpp exists (its gppVersion, cmpStatus, signalStatus, and applicable sections), and whether Google Consent Mode default/update pushes appear in window.dataLayer, folded into a final granted/denied state for all seven consent keys (ad_storage, analytics_storage, ad_user_data, ad_personalization, functionality_storage, personalization_storage, security_storage).

How it works

The decoding engine (consent.js) is a dependency-free bit-level parser, not a wrapper around a vendor SDK.

Base64url to bits. Both TCF and GPP use websafe base64url without padding. The engine maps each character to exactly 6 bits, most-significant bit first, producing one long bit string. Any character outside the 64-character alphabet throws immediately — which is how malformed pastes get rejected rather than half-decoded.

TCF core segment. The parser reads the segment before the first . with a forward bit reader, consuming fields in the exact order the IAB spec lays them out: a 6-bit version (must equal 2, or decoding fails with the version it found), two 36-bit timestamps stored as deciseconds since the epoch (multiplied by 100 to get milliseconds, then formatted as ISO dates), 12-bit CMP ID and version, the consent screen, a two-letter language field encoded as offsets from “A”, the vendor-list and policy versions plus two flag bits, then the fixed-width bitfields: 12 bits of special-feature opt-ins, 24 bits of purpose consents, and 24 bits of legitimate-interest transparency, where each set bit at position n means ID n+1.

Vendor consents, two encodings. The vendor section starts with a 16-bit max vendor ID and a one-bit encoding flag. If it is a plain bitfield, the reader collects every set bit up to the max ID. If it is range-encoded, it reads a 12-bit entry count and then each entry as either a single 16-bit vendor ID or a start/end pair, expanding ranges into the full ID list. The output tells you which encoding the CMP used.

GPP header and Fibonacci coding. A GPP string is header~section~section.... The engine decodes only the header: a 6-bit type that must equal 3, a 6-bit version, and the section-ID list — which GPP encodes with Fibonacci coding, a variable-length scheme where each integer is a sum of Fibonacci numbers terminated by a 11 bit pair, and each value is an offset from the previous one. The parser implements this directly, then maps IDs to framework names from a built-in registry of 19 known sections (IDs up to 22) and reports how many payload segments follow the header.

Auto-detection. The trick is in the first character. A TCF v2 core string always begins with C (version 2 = bits 000010), and a GPP header always begins with D (type 3 = bits 000011). The detect() function tries GPP first when the input contains ~ or starts with D, falls back to TCF, and tries GPP once more before giving up with the TCF error message.

Consent Mode folding. The dataLayer reader scans for gtag-style ['consent', 'default'|'update', {...}] entries and folds them in order, so later update calls override earlier default values — the same semantics gtag itself applies. It is a pure function over the array, so it never misreads unrelated dataLayer events.

How to use it

  1. Open the Consent / CMP Checker and stay on the Decode a string tab.
  2. Paste a consent string — from a CMP cookie, an ad request URL, or the output of __tcfapi('getTCData', 2, console.log) in your DevTools console. Paste the base64url value only, with no cookie name or surrounding JSON.
  3. Press Decode (or Ctrl/Cmd+Enter). A status chip confirms “TCF v2 string” or “GPP string”; unrecognised input is flagged with the reason.
  4. No string handy? The TCF sample button loads a valid self-generated all-consent placeholder string, and GPP sample loads DBABL, a real GPP header for the US National section.
  5. To inspect a live page, switch to the This page tab and press Read this page’s signals. The TCF and GPP APIs are asynchronous, so the tool waits briefly for their callbacks — if a banner loads late, press the button again after it appears.

Limitations

  • The signal, not the enforcement. A consent string is the CMP’s record of a user’s choices. Decoding it cannot tell you whether any tag on the page actually read it or gated on it — a pixel can fire in full violation of a perfectly-formed string. The tool states this on the page.
  • TCF core segment only. Publisher TC, disclosed-vendor, and other segments after the first . are not decoded, and only version 2 strings are accepted.
  • GPP header only. Section payloads (the actual consent bits per US state framework) are out of scope; you get the section list, not their contents.
  • “This page” sees only what a CMP exposes. Most pages run no CMP at all, and on those every check reads “not present” — expected, not a bug. On webtracking.org itself the check shows nothing, because the site runs no CMP.
  • Static name tables. Purpose and section names come from built-in TCF v2.2 / GPP lookup tables, not a live fetch of the Global Vendor List — unknown IDs are shown by number.

FAQ

Where do I find a TC string to paste? In DevTools: run __tcfapi('getTCData', 2, d => console.log(d.tcString)) on a page with a TCF CMP, or copy the value of the CMP’s consent cookie (commonly euconsent-v2), or pull the gdpr_consent parameter off an ad request. Paste just the string itself.

Why does my string decode as “not recognised”? Usually one of three things: it is a TCF v1 or other-version string (the version field must be 2), it includes surrounding JSON or a cookie name, or a character outside the base64url alphabet crept in during copy-paste. The error message reports which check failed.

Does the tool send my consent string anywhere? No. Decoding is pure client-side JavaScript; the page makes no network requests and works offline. There is no account, no server, and no logging.

Can it tell me whether consent is being respected? No — and no string decoder can. It shows what was recorded, not what tags did with it. Checking whether tags actually gate on the signal requires auditing what the site loads, which is what Tag Reaper (ad.rip) does.

What is the difference between “purposes consented” and “legitimate interest”? They are two separate bitfields in the string. Consent purposes required an affirmative user choice; legitimate-interest purposes are ones the vendor claims a legal basis for without consent, with the string recording transparency (and any objection handled elsewhere).