Skip to documentation content

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:

StoredNotes
Category decisionsnecessary, analytics, marketing, preferences — booleans; necessary is always true
Consent IDOpaque identifier generated in the browser and kept in the consent cookie
Client timestampThe time the browser reported, after clamping (below)
Server timestampWritten by the database on insert; this is the authoritative one
Banner and teamResolved server-side from the public banner ID in the payload
Event typeOne of consent_given, consent_changed, consent_default, accept_all, accept_necessary
Collection methodHow the state was collected — see the table below
Default-state flagTrue when the row was machine-written rather than chosen
Consent modeoptIn, optOut or notice, plus the mode actually resolved at runtime
Matched rule and geo sourceWhich regional rule matched, and how the location was determined
Country and regionCoarse location only, as provided by the edge
DomainThe domain the banner ran on
ReferrerRedacted to the banner's configured grouping level and truncated
Privacy signalgpc, dnt or gpc_dnt when one was present, plus a flag recording that it shaped the state
US purpose choicesSale/sharing, targeted advertising and sensitive data, when the visitor used the opt-out panel
Consent stringPopulated 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 centreVersioned /api/v1/consent/log
IP addressWritten as null, deliberatelyResolved from the request and stored
User agentWritten as null, deliberatelyTaken from the request or its body, and stored
Page URLDomain onlyFull sanitized referer
ReferrerTruncated and redacted to the banner's grouping levelFull sanitized referer
Origin restricted to the banner's domainsYesNo
Category, collection method, default-state, privacy-signal fieldsWrittenNot 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:

ValueMeaning
bannerChosen in the consent banner
preference_centerSaved in the hosted preference centre — this is the value the preference-centre endpoint writes
preferencesDeclared by the runtime's own canonical state for a preference-centre change
apiSet through the browser API by your own code
default_optinOpt-in default that grants nothing — an audit row, recorded only under experiments
default_optoutGranted at load under an opt-out model
default_noticeGranted at load under a notice model
hidden_autograntGranted 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.

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.

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.