Consent records
The fields stored for every consent event, how explicit choices are distinguished from defaults, and what a signed receipt proves.
Every consent event your banner reports is written to a single row. This page lists what that row contains, because a record you cannot describe field by field is not evidence you can rely on.
What is stored
The banner posts to the consent endpoint, the server validates the payload and writes one row:
| Stored | Notes |
|---|---|
| Category decisions | necessary, analytics, marketing, preferences — booleans; necessary is always true |
| Consent ID | Opaque identifier generated in the browser and kept in the consent cookie |
| Client timestamp | The time the browser reported, after clamping (below) |
| Server timestamp | Written by the database on insert; this is the authoritative one |
| Banner and team | Resolved server-side from the public banner ID in the payload |
| Event type | One of consent_given, consent_changed, consent_default, accept_all, accept_necessary |
| Collection method | How the state was collected — see the table below |
| Default-state flag | True when the row was machine-written rather than chosen |
| Consent mode | optIn, optOut or notice, plus the mode actually resolved at runtime |
| Matched rule and geo source | Which regional rule matched, and how the location was determined |
| Country and region | Coarse location only, as provided by the edge |
| Domain | The domain the banner ran on |
| Referrer | Redacted to the banner's configured grouping level and truncated |
| Privacy signal | gpc, dnt or gpc_dnt when one was present, plus a flag recording that it shaped the state |
| US purpose choices | Sale/sharing, targeted advertising and sensitive data, when the visitor used the opt-out panel |
| Consent string | Populated only when the framework producing it is switched on; see the note at the end of this page |
Two ingestion paths, two sets of stored fields
Consent records reach the store through more than one endpoint, and they do not store the same fields. Read this before you describe CookieBeam's data handling in your own records of processing.
| Banner and preference centre | Versioned /api/v1/consent/log | |
|---|---|---|
| IP address | Written as null, deliberately | Resolved from the request and stored |
| User agent | Written as null, deliberately | Taken from the request or its body, and stored |
| Page URL | Domain only | Full sanitized referer |
| Referrer | Truncated and redacted to the banner's grouping level | Full sanitized referer |
| Origin restricted to the banner's domains | Yes | No |
| Category, collection method, default-state, privacy-signal fields | Written | Not written |
The banner path is the one the shipped script uses, and it sets the IP and user agent to null on purpose — the IP for privacy, the user agent to avoid holding fingerprinting material. The preference centre writes the same way.
The versioned endpoint is a separate, older interface. If your integration posts consent to it, the records it writes do carry direct identifiers, and the minimization described above does not apply to them. Which path wrote a record is therefore a question worth being able to answer about your own data.
If you are not sure whether anything in your stack posts to the versioned endpoint, treat that as an open question rather than assuming the minimized behaviour. Records from the two paths are visibly different: only versioned-endpoint rows have an IP address or user agent, and only banner rows carry an event type and collection method.
Timestamp clamping
Browser clocks are wrong more often than people expect, and a skewed clock corrupts ordering. If the reported time is more than five minutes in the future or more than twenty-four hours in the past, the server replaces it with server time. The database's own insert timestamp is always authoritative regardless.
Explicit choices versus defaults
This is the distinction an auditor asks about first, so the schema carries it explicitly rather than leaving it to be inferred.
A row is machine-written when the runtime recorded a state the visitor never chose — an opt-out or notice model granting at page load, or an auto-grant in a region configured to hide the banner. Those rows are flagged, and the flag changes system behaviour:
- No webhook is dispatched. A machine-written default must never tell your downstream systems "this visitor consented".
- It is not billed as a consent log.
- US purpose choices are forced to null, server-side. Those values exist only after a visitor actively saves the opt-out panel, so a default row cannot carry them.
- It is not counted as a user action in banner metrics.
The collection method records the provenance:
| Value | Meaning |
|---|---|
banner | Chosen in the consent banner |
preference_center | Saved in the hosted preference centre — this is the value the preference-centre endpoint writes |
preferences | Declared by the runtime's own canonical state for a preference-centre change |
api | Set through the browser API by your own code |
default_optin | Opt-in default that grants nothing — an audit row, recorded only under experiments |
default_optout | Granted at load under an opt-out model |
default_notice | Granted at load under a notice model |
hidden_autogrant | Granted in a region configured not to show a banner |
Two of those values describe the same event. The preference-centre endpoint writes preference_center, while the runtime's own canonical consent state uses preferences for a preference-centre change. Match on both if you are classifying records by provenance.
If your reporting treats every stored row as "a visitor consented", it will overstate consent for any region you run in opt-out or notice mode. Filter on the default-state flag.
Preference-centre links
When an explicit consent row is written and carries a consent ID, the server issues an opaque preference-centre link and returns it to the banner. The link is a random token, valid for thirty days, that resolves to that consent record — so a visitor can revisit their choice without you exposing internal banner or team identifiers.
Signed receipts
A receipt turns a stored row into a portable, verifiable artifact. Requesting one requires both the consent log ID and the matching consent ID, compared in constant time; the request must also come from a domain authorized for that banner.
The receipt is a signed token whose payload contains the receipt and consent log IDs, the public banner ID, a subject hash, the four category decisions, the US purpose choices, the event type and collection method, the privacy signal, the default-state flag, the recorded timestamp, and the origin the receipt was issued for. The subject hash is a SHA-256 hash of the consent ID — the receipt therefore carries a pseudonymous reference, not an email address or a name.
Verification is a separate public endpoint, and current receipts require you to pass the origin you expect.
What verification proves:
- the token was signed with a CookieBeam signing key published at the verification-keys endpoint, and
- its contents have not been altered since signing, and
- it was issued for the origin you supplied.
What verification does not prove:
- who the person behind the pseudonymous subject hash is,
- that the banner was configured correctly, or that your categories describe your actual tags,
- that the visitor saw, read, or understood the banner,
- anything about a row that was machine-written — a receipt for a default row faithfully reports that it was a default, and the flag is inside the signed payload for exactly that reason.
A receipt is proof of what was recorded and that the recording is intact. It is not proof that the recording was lawful.
A note on the consent string field
The record has a column for a transparency-and-consent-framework string. TCF support is built but not enabled in production — it is gated behind an environment flag that ships disabled by default, and the shipped configuration also has no vendor identifier set. The column therefore stays empty unless a deployment turns it on deliberately. Do not plan around it as an available feature, and do not describe your site as operating under that framework on the strength of this column existing.