TCF & GPP Registration Guide
cmpId from the relevant body before you can legally rely on the strings Consenti generates. This page covers that registration step, how to tell Consenti it happened, and the technical implementation underneath.Why confirmation is separate from config
Every framework below follows the same shape: you register externally, put the resultingcmpId/cmpVersionin Consenti's static server config, restart the server, then confirm the registration once in the dashboard. Until confirmed, Consenti treats the framework as disabled everywhere it matters — no TC/GPP string is generated, and the widget does not install window.__tcfapi / window.__gpp. This is deliberate: a live cmpIdthat isn't actually yours (a typo, a config left over from a demo, a deregistered ID) must never silently start signing consent strings.
compliance.tcf and compliance.gpp live only in your static server config file — Consenti never lets these be edited from the dashboard, since a config file under version control with a required server restart is a much harder thing to change by accident (or by an attacker with dashboard access) than a database row. What Consenti stores server-side is a hash of the last-confirmed cmpId/cmpVersion (and, for TCF, publisherCC) — never the raw values. Every relevant request recomputes the hash of the live config and compares it. A mismatch — including "never confirmed" — fails closed.
Practically, this means re-confirmation is required whenever:
- You change
cmpIdorcmpVersion(e.g. after a re-registration) - TCF: you change
publisherCC - You never confirmed in the first place on a fresh install
Turning a framework off (enabled: false) is the one change that always takes effect immediately, with no confirmation needed — disabling never requires proof of anything.
TCF v2.3
general-privacy-consent— full-flexibility mode for programmatic advertising consent via IAB Europe's TCF framework. Use compliance: { type: 'general-privacy-consent' } in your ConsentiSetup config.IAB Europe's Transparency and Consent Framework (TCF) v2.3 is the standard for programmatic advertising consent. It is required for CMPs operating in the IAB ecosystem (RTB, DSPs, SSPs).
When you need TCF
TCF is needed if your site uses:
- Programmatic advertising (RTB / header bidding)
- Google Ad Manager with EU consent mode
- Any ad tech vendor registered in the IAB Global Vendor List (GVL)
window.__tcfapi interface is fully implemented, and the backend fetches/caches the real Global Vendor List. Spec-correct binary TC-string encoding is available when you set compliance.tcf.publisherCC and install the optional @iabtechlabtcf/corepeer dependency — without both, Consenti falls back to a simplified, non-spec format. Known gap: no user-facing legitimate-interest objection control yet (legitimate-interest signals are derived from GVL vendor metadata, not a per-visitor opt-out). Spec-correct encoding is a prerequisite for ad-exchange acceptance, not a substitute for the IAB registration below — "partial" describes encoding fidelity, not compliance coverage.1. Register with IAB Europe
Registration is free and done directly with IAB Europe, independently of Consenti. You will receive a cmpId and a cmpVersion once approved.
2. Configure Consenti
// Server config (@consenti/api)
compliance: {
tcf: {
enabled: true,
cmpId: 9999, // from IAB Europe
cmpVersion: 1,
publisherCC: 'DE', // optional — required for spec-correct binary TC strings
},
},Restart the server so the new config is picked up, then mirror it on the widget:
createConsenti({
compliance: {
tcf: { enabled: true, cmpId: 9999, cmpVersion: 1 },
},
})3. Confirm registration in the dashboard
Open Dashboard → Vendors. While TCF is enabled and unconfirmed, a non-dismissible panel shows your live cmpId/cmpVersion and a required checkbox: "TCF enabled, and we have registered this cmpId with IAB Europe." Checking it and confirming validates the cmpIdagainst IAB's cached public CMP List (the same vendor-list.consensu.org host the GVL comes from):
- Deregistered/expired — hard error, confirmation is blocked.
- Not found yet — soft-blocked with a Refresh Status button (bypasses the 7-day cache) — registrations can take time to propagate to the list.
- Found and active — confirmed. The panel disappears.
The panel is not dismissible without confirming — it's computed live from whether the stored hash matches the current config, not a flag you can acknowledge-and-hide.
TCF admin API
| Route | Purpose |
|---|---|
GET /consenti/admin/v1/tcf/registration-status | Current cmpId/cmpVersion, confirmation state, and a live CMP-List lookup. Add ?refresh=true to bypass the cache. Requires settings:update. |
POST /consenti/admin/v1/tcf/confirm-registration | Body { acknowledge: true }. Validates against the CMP List and, on success, stores the confirmation hash. Requires settings:update. |
GET /consenti/api/v1/tcf/status | Public, unauthenticated, always-live. Returns { blocked: boolean } — this is what the widget checks at init to decide whether to install window.__tcfapi. |
What Consenti implements
Backend
compliance.tcf.enabled: true— activates TCF mode- Global Vendor List (GVL) is fetched and cached for 7 days
tcf_stringcolumn is added to consent recordsPOST /consentacceptstcfStringin the payloadGET /consent/:visitorIdreturnstcfStringwhen present
TC string format
By default Consenti uses a simplified base64url-encoded JSON format for TC strings — not the IAB binary bitfield encoding. For spec-correct binary encoding, set compliance.tcf.publisherCC (your ISO 3166-1 alpha-2 publisher country code) and install the optional peer dependency:
npm install @iabtechlabtcf/coreThat's the actively-maintained IAB Tech Lab package — not iabtcf-core, which doesn't exist on npm, and not @iabtcf/core, which hasn't been published since 2023. Once installed, the backend automatically uses it for every consent record with publisherCC configured, no code changes needed. Without it installed, or without publisherCC set, Consenti falls back to the simplified format automatically. Internally, this is what @consenti/api does for you (apps/api/src/tcf/real-tc-string.ts):
import { TCModel, TCString, GVL } from '@iabtechlabtcf/core'
const model = new TCModel(new GVL(vendorListJson))
model.cmpId = 9999
model.purposeConsents.set([1, 2, 3])
const tcString = TCString.encode(model)Frontend (__tcfapi stub)
The __tcfapi stub lives in @consenti/ui and is enabled bycompliance.tcf: { enabled: true, cmpId, cmpVersion }(must match the backend's compliance.tcf config above). It implements the four required commands:
| Command | Description |
|---|---|
getTCData | Returns the TC string and consent status |
ping | Returns CMP status (required for IAB compliance) |
addEventListener | Registers a listener for consent updates |
removeEventListener | Unregisters a listener |
window.__tcfapiper page. If it's already set — a real CMP, or another ConsentiSetupinstance on a multi-profile page — Consenti's stub does not overwrite it. The widget doesn't fetch the GVL itself, so gvlVersionin getTCData is a placeholder (0) — only the backend's admin-only/tcf/vendors//tcf/purposes routes reflect the real GVL.GVL caching
The GVL is fetched once at startup and refreshed every 7 days. If the fetch fails, the cached version is returned. To force a refresh, restart the server.
Mapping cookies to GVL vendors
In the dashboard Consent Template Editor, enable the TCF Vendors column toggle. Each cookie row gains a vendor picker that searches the GVL by name. Selecting a vendor auto-fills tcfVendorId and tcfPurposes on the cookie. Only cookies with a tcfVendorId that the visitor granted contribute to the TC string.
Testing
Use the IAB TCF Validator to validate your TC string before going live. The validator checks that your CMP ID is registered and that the TC string is well-formed.
Official references
- IAB Europe — TCF v2.3 specification
- IAB Europe — CMP registration portal
- IAB Global Vendor List (GVL) — live JSON
- IAB Europe — TCF supporting resources and policy
GPP (US National section)
usnatsection only — the umbrella section most MSPA/programmatic US bidders read. Individual state sections (US-CA, US-CO, etc.) aren't generated separately.cmpIdagainst. Confirming in the dashboard means you are personally attesting the registration is real; it is not independently verified the way TCF's is.1. Register
GPP's US National section exists to carry MSPA (Multi-State Privacy Agreement) and CCPA/CPRA-style opt-out signals. If your data transactions are covered by MSPA, registration/signatory status is handled through the Multi-State Privacy Agreement itself, not a Consenti-specific step:
2. Configure Consenti
// Server config (@consenti/api)
compliance: {
gpp: {
enabled: true,
cmpId: 9999,
cmpVersion: 1,
mspaCoveredTransaction: true,
// IAB tri-state: 0 = not applicable, 1 = yes, 2 = no
mspaOptOutOptionMode: 1,
mspaServiceProviderMode: 2,
},
},createConsenti({
compliance: {
gpp: {
enabled: true,
cmpId: 9999,
cmpVersion: 1,
mspaCoveredTransaction: true,
mspaOptOutOptionMode: 1,
mspaServiceProviderMode: 2,
},
},
})The GPP string is only generated for a given consent write when the profile actually contains cookies tagged cpraCategory: 'sale' or 'sharing' — sale/sharing/targeted-advertising opt-out and notice fields are derived from those cookie tags. A profile with no such cookies produces no GPP string, regardless of config.
3. Confirm registration in the dashboard
Same panel location as TCF — Dashboard → Vendors— with the same non-dismissible-while-unconfirmed behavior and required checkbox, minus the CMP-List lookup (there being none to check against) and minus a "Refresh Status" button.
GPP admin API
| Route | Purpose |
|---|---|
GET /consenti/admin/v1/gpp/registration-status | Current cmpId/cmpVersion and confirmation state. Requires settings:update. |
POST /consenti/admin/v1/gpp/confirm-registration | Body { acknowledge: true }. Stores the confirmation hash directly — no external lookup. Requires settings:update. |
GET /consenti/api/v1/gpp/status | Public, unauthenticated, always-live. Returns { blocked: boolean } — checked by the widget before installing window.__gpp. |
Spec-correct binary encoding
By default Consenti falls back to a simplified, non-spec base64url-encoded GPP string. For real IAB-spec binary encoding, install the optional peer dependency server-side (this never affects the widget bundle — the encoder only runs in @consenti/api):
npm install @iabgpp/cmpapiOnce installed, every GPP-eligible consent write automatically uses it; without it, Consenti logs a one-time warning and falls back to the simplified format. Same fallback shape as TCF's @iabtechlabtcf/core.
DPDPA (India)
opt-in-dpdpa compliance group (data fiduciary name, grievance officer, age gate) already covers the consent-flow requirements. See the DPDPA compliance guidefor what's implemented today. This section will be filled in if/when a registration-governance step is added for it.