All endpoints

DocsAPIWebhooks

Webhookconsent.recorded

Consent decisions were recorded

Batched: at most one POST a minute per site, covering every record since the last one.

Delivery

We POST this to your endpoint, signed with its secret. Check the signature before trusting the body.

Headers

  • X-TagSentry-Signaturestringin headerrequired

    t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">, keyed with the endpoint's secret.

  • X-TagSentry-Eventstringin headerrequired

    The event type; equal to the body's type.

    Always consent.recorded

  • X-TagSentry-Deliverystringin headerrequired

    The delivery id; equal to the body's id. Stable across retries.

Body

  • idstringrequired

    The delivery id. Stable across retries and equal to X-TagSentry-Delivery: dedupe on it.

  • typestringrequired

    Always consent.recorded

  • versionintegerrequired

    Moves only when a receiver could notice. A new optional field does not move it.

  • createdAtstringrequired

    ISO-8601 timestamp, UTC.

  • accountIdstringrequired
  • siteobjectrequired
  • site.idstringrequired
  • site.domainstringrequired
  • site.regionstringrequired
  • dataobjectrequired
  • data.afterstring | nullrequired

    Records strictly after this were counted. Null on an endpoint's first batch.

  • data.throughstringrequired

    Records up to and including this were counted.

  • data.totalintegerrequired
  • data.byMethodmap of integerrequired
  • data.recordIdsarray of stringrequired

    Oldest first, capped. Fetch a record with GET /sites/{siteId}/consent-records.

  • data.recordIdsTruncatedbooleanrequired

Example body

{  "id": "3f6c1b8e-2d4a-4c7e-9a51-0b8f2e6d7c10",  "type": "consent.recorded",  "version": 0,  "createdAt": "2026-09-26T14:02:00.000Z",  "accountId": "3f6c1b8e-2d4a-4c7e-9a51-0b8f2e6d7c10",  "site": {    "id": "3f6c1b8e-2d4a-4c7e-9a51-0b8f2e6d7c10",    "domain": "silverpine.example",    "region": "…"  },  "data": {    "after": "2026-09-26T14:02:00.000Z",    "through": "2026-09-26T14:02:00.000Z",    "total": 0,    "byMethod": {      "key": 0    },    "recordIds": [      "3f6c1b8e-2d4a-4c7e-9a51-0b8f2e6d7c10"    ],    "recordIdsTruncated": false  }}

Answer with any 2xx. Anything else counts as a failed attempt.

Delivery and signing

Counts and record ids, never visitor data.

Verifying a delivery. Every POST carries X-TagSentry-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256, keyed with the endpoint's secret (whsec_…, shown once when the endpoint is added), of the string t + "." + rawBody: the timestamp, a full stop, then the body EXACTLY as received, before any JSON parsing. Compare in constant time and refuse a t more than 300 seconds from your clock.

const crypto = require("node:crypto");
function verify(secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(nowSeconds - t) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

X-TagSentry-Event names the event; X-TagSentry-Delivery is the delivery id, stable across retries. Answer 2xx within 10 seconds. A timeout, 408, 429 or 5xx is retried with backoff, 8 tries in all; any other answer, and a redirect, fails at once. We never follow redirects.

From the OpenAPI document, version 2026-08-26. Raw OpenAPI