Skip to documentation content

Form consent

Record, search, export and erase proofs of consent collected by your own sign-up, newsletter and contact forms.

The form consent endpoints keep an append-only record of consent that people give on your own forms — a newsletter sign-up, a contact form, an account registration — and return a signed receipt for each record. You can record from your server with a secret key, or straight from the browser with the forms snippet and a publishable key.

Availability

  • Switched on per deployment. Form consent is off unless it has been switched on for the CookieBeam deployment you use. While it is off, every endpoint on this page (and every marketing preferences endpoint) answers HTTP 404 feature_disabled, and the dashboard's Form consents section says that recording is not switched on.
  • Plan. Recording needs a plan that includes API access; otherwise HTTP 403 plan_required.
  • Monthly quota. New given and confirmed proofs count against a monthly quota that depends on the plan. Past it, the call returns HTTP 429 quota_exceeded with resetAt and Retry-After, and nothing is stored. Withdrawals never count.
  • After a downgrade. Keys keep working for withdrawals and erasure, so people can still withdraw consent and you can still honour erasure requests. New given and confirmed proofs are refused with HTTP 403 plan_required.

How a proof works

  • Events. A proof records one of three events: given (the person ticked one or more consent boxes), confirmed (a double opt-in confirmation of an earlier given proof, named by confirmsId), or withdrawn (the person withdrew one or more purposes).
  • Purposes. For given, the purposes are the keys of the ticked checkboxes. A later record for the same person and purpose supersedes the earlier one; superseded proofs stay stored with current: false.
  • The person. You send the person's e-mail address, phone number or your own id as subject. CookieBeam normalises it and stores only a team-scoped keyed hash; the value itself is never returned. Responses identify the person by subjectRef (<key id>:<hex hash>), which you can use to filter lists. To find a person by their e-mail address or phone number, use search, which keeps the identifier out of URLs and logs.
  • Time. collectedAt is the time the person submitted the form. A value more than 24 hours from server time is replaced by the time the request arrived, and the proof carries clientTimeRejected: true.
  • Channel. Proofs recorded with a publishable key have channel: "browser"; proofs recorded with a secret key have channel: "server".
  • Receipts. Recording a proof, getting one proof and exporting return a signed receipt: a JWS (type cb-form-consent+jws) you can verify against the public keys at /.well-known/cookiebeam-jwks.json. It is null only while signing is temporarily unavailable. List and search results do not carry receipts.
  • Webhooks. A stored given or confirmed proof emits consent.form_recorded, a withdrawal emits consent.form_withdrawn. See Webhooks.

Record a proof

POST /api/v1/consent/forms

Requires a live secret key with consent:forms:write, or a publishable key from one of its allowed origins. Publishable keys can only record given; confirmations and withdrawals need a secret key, because they change the state of a person you have verified on your side. Accepts an Idempotency-Key. Bodies are limited to 64 KB.

FieldTypeRequiredNotes
formIdstringyesYour stable id for the form, 1–100 characters, for example newsletter-footer
formNamestringnoUp to 200 characters
bannerIdstring (UUID)noThe public Banner ID of the site the form is on. Required for a key restricted to specific banners
eventstringnogiven (default), confirmed, or withdrawn
confirmsIdstring (UUID)for confirmedThe proof id the double opt-in confirms
subject.typestringyesemail, phone, or external_id
subject.valuestringyesThe e-mail address, phone number or your own id, 1–320 characters. Hashed, never returned
consentTextstringfor givenThe consent statement shown, verbatim, up to 10,000 characters
checkboxesarrayfor givenUp to 50 entries of { key, label, checked, required? }. key is the purpose (letters, digits, _ . : -); label is the text shown next to the box
purposesstring[]nowithdrawn only: the purposes withdrawn. Default: every purpose currently given
documentsarraynoUp to 20 documents the person agreed to: { type, documentId?, versionId?, version?, url?, contentHash? }. type is privacy_policy, terms_of_service or your own type. Reference a CookieBeam-hosted legal document by documentId / versionId, or an external one by url
sourceUrlstring (URL)noThe page the form is on
languagestringno2–16 characters
collectedAtstring (ISO 8601)noWhen the person submitted the form

A given proof must have at least one checked box, and every box marked required must be checked.

curl -X POST https://cookiebeam.com/api/v1/consent/forms \
  -H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "formId": "newsletter-footer",
    "formName": "Newsletter",
    "subject": { "type": "email", "value": "alice@example.com" },
    "consentText": "Send me the monthly newsletter. I can unsubscribe at any time.",
    "checkboxes": [
      { "key": "newsletter", "label": "Send me the monthly newsletter.", "checked": true, "required": true },
      { "key": "offers", "label": "Send me partner offers.", "checked": false }
    ],
    "documents": [
      { "type": "privacy_policy", "url": "https://example.com/privacy", "version": "7" }
    ],
    "sourceUrl": "https://example.com/blog",
    "language": "en",
    "collectedAt": "2026-10-22T09:14:58.000Z"
  }'

A stored proof returns HTTP 201:

{
  "data": {
    "id": "7b9e2c14-6a3d-4f58-9e01-2c3d4e5f6a7b",
    "event": "given",
    "subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "purposes": ["newsletter"],
    "receipt": "eyJhbGciOiJFZERTQSIsInR5cCI6ImNiLWZvcm0tY29uc2VudCtqd3MifQ…",
    "recordedAt": "2026-10-22T09:15:00.412Z",
    "clientTimeRejected": false
  },
  "version": "v1"
}

A test publishable key validates the request and stores nothing: the response is HTTP 200 with test: true, a zero id and a placeholder subjectRef.

To confirm a double opt-in, send event: "confirmed" with confirmsId and the same subject. To withdraw, send event: "withdrawn" with the subject and, optionally, purposes.

List proofs

GET /api/v1/consent/forms

Requires consent:forms:read. Test keys work. Newest first, cursor-paginated.

Query parameterNotes
limit, cursorPage size 1–200 (default 50), and the nextCursor of the previous page
subjectRefOnly this person's proofs
formId, purposeFilter by form or purpose
eventgiven, confirmed, or withdrawn
from, toISO 8601 date-times
bannerIdPublic Banner ID: only proofs recorded for that site
{
  "data": {
    "items": [
      {
        "id": "7b9e2c14-6a3d-4f58-9e01-2c3d4e5f6a7b",
        "formId": "newsletter-footer",
        "formName": "Newsletter",
        "event": "given",
        "confirmsId": null,
        "subjectType": "email",
        "subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "consentText": "Send me the monthly newsletter. I can unsubscribe at any time.",
        "consentTextHash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
        "checkboxes": [
          { "key": "newsletter", "label": "Send me the monthly newsletter.", "checked": true, "required": true },
          { "key": "offers", "label": "Send me partner offers.", "checked": false, "required": false }
        ],
        "purposes": ["newsletter"],
        "documents": [{ "type": "privacy_policy", "url": "https://example.com/privacy", "version": "7" }],
        "sourceUrl": "https://example.com/blog",
        "language": "en",
        "collectedAt": "2026-10-22T09:14:58.000Z",
        "clientTimeRejected": false,
        "channel": "server",
        "current": true,
        "recordedAt": "2026-10-22T09:15:00.412Z"
      }
    ],
    "nextCursor": null
  },
  "version": "v1"
}

Get one proof

GET /api/v1/consent/forms/{id}

Requires consent:forms:read. Returns { proof, receipt }: the proof in the list shape and a freshly signed receipt. A proof of another team, or outside a banner-restricted key's list, returns HTTP 404 not_found.

Find every proof for a person

POST /api/v1/consent/forms/search

Requires consent:forms:read. Send the identifier in the body, so it never appears in a URL or a log. The search covers every hashing key the team has used, so proofs recorded before a key rotation are found too. Returns a page in the list shape.

curl -X POST https://cookiebeam.com/api/v1/consent/forms/search \
  -H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "subject": { "type": "email", "value": "alice@example.com" }, "limit": 50 }'
FieldNotes
subjectRequired: { type, value }
formId, purpose, eventOptional filters
limit, cursorOptional paging

A person's current state

POST /api/v1/consent/forms/subjects/state

Requires consent:forms:read. Body: { "subject": { "type", "value" } }. Returns the latest record per purpose:

{
  "data": {
    "subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "proofCount": 2,
    "purposes": [
      {
        "purpose": "newsletter",
        "status": "confirmed",
        "proofId": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
        "formId": "newsletter-footer",
        "updatedAt": "2026-10-22T09:20:11.000Z"
      }
    ]
  },
  "version": "v1"
}

Export proofs

GET /api/v1/consent/forms/export

Requires consent:forms:export. Test keys work. Takes the same filters as the list (without paging) plus format: json (default, { "proofs": [ … ] }) or csv. The response streams as a file download, newest first, with a freshly signed receipt on every row. The number of rows is capped per plan; the cap is in the X-Export-Row-Cap header. Each export is written to the team's audit log.

curl "https://cookiebeam.com/api/v1/consent/forms/export?format=csv&from=2026-01-01T00:00:00Z" \
  -H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
  -o form-consents.csv

Erase a person's proofs

DELETE /api/v1/consent/forms/subjects

Requires consent:forms:erase, a live key, and a key that is not restricted to specific banners (erasure covers every banner of the team). Body: { "subject": { "type", "value" } }. Deletes the person's proofs under every hashing key the team has used and writes a minimal erasure record (subject reference, count, key) to the team's audit log.

{
  "data": {
    "subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "deleted": 2
  },
  "version": "v1"
}

deleted is 0 when nothing was stored.

Browser forms snippet

The forms snippet records a given proof when a marked form is submitted. It never delays or blocks the submission, and it reads only the fields you mark.

<script
  src="https://cookiebeam.com/forms/v1/cookiebeam-forms.js"
  data-key="cb_pub_live_your_publishable_key"
  data-banner-id="PUBLIC_BANNER_ID"
  defer
></script>

<form data-cookiebeam-consent-form="newsletter-footer" name="Newsletter">
  <input type="email" name="email" data-cookiebeam-subject="email">
  <label>
    <input type="checkbox" data-cookiebeam-consent="newsletter" required>
    Send me the monthly newsletter.
  </label>
  <p data-cookiebeam-consent-text>Send me the monthly newsletter. I can unsubscribe at any time.</p>
  <a href="https://example.com/privacy" data-cookiebeam-document="privacy_policy" data-version="7">Privacy policy</a>
  <button type="submit">Subscribe</button>
</form>
AttributeOnMeaning
data-keyscriptThe publishable key
data-banner-idscriptOptional public Banner ID, sent as bannerId
data-apiscriptOptional API origin. Default: the origin the script was loaded from
data-cookiebeam-consent-formformMarks the form and sets formId
data-cookiebeam-subjectinputThe field that identifies the person: email, phone or external_id. Without it, the first type="email" input is used
data-cookiebeam-consentcheckboxA consent box; the value is the purpose key. required boxes must be ticked
data-cookiebeam-consent-textany elementThe consent statement. Without it, the labels of the ticked boxes are used
data-cookiebeam-documentlinkA document the person agrees to; data-version adds its version

The snippet records nothing when there is no subject value, when no consent box is ticked, or when a required box is unticked. It sends the page's origin and path as sourceUrl, never the query string. Your dashboard's Form consents section shows the snippet with your key filled in.

The snippet also exposes window.CookieBeamForms:

MemberDescription
versionThe snippet version, currently 1.0.0
collect(form)Build the request body from a marked form, or null when there is nothing to record
record(payload)Send a body you built yourself (same shape as POST /api/v1/consent/forms, given only). Resolves to the parsed response

Errors

HTTP statusCodeCause
400invalid_requestValidation failed, for example no checked box, a required box unchecked, a missing consentText or confirmsId, or a withdrawn proof being confirmed
403plan_requiredThe plan does not include form consent, or the plan was downgraded and the event is not a withdrawal
403publishable_key_not_allowedA publishable key was used for a confirmation, a withdrawal, or any endpoint other than recording
403origin_not_allowedThe publishable key does not list the request's origin
403key_restrictedA banner-restricted key recorded without one of its banners, or tried to erase
403test_key_read_onlyA secret test key was used to record or erase
404feature_disabledForm consent is not switched on for this deployment
404not_foundThe banner, the proof, or the proof to confirm was not found
429quota_exceededThe monthly quota is used up; resetAt says when it resets
503identity_unavailableIdentifier hashing is temporarily unavailable; retry later