Webhooks.

Consent decisions arrive in your own systems as they happen, signed so you can prove they came from us.

What this is for

A withdrawal that only reaches this dashboard leaves your CRM, your warehouse and your analytics acting on a consent the visitor has taken back. Point an endpoint at your own service and every decision arrives there too. Add one under Developer in the dashboard; the workspace owner manages them.

EventSent when
consent.recordedA visitor records or changes a decision, including a withdrawal. The highest volume by a wide margin.
configuration.publishedAn administrator publishes a new configuration version for a site.
pingOnly the “Send test” button. Subscribe a new endpoint to this while you are building against it.

What arrives

Every delivery is a POST with a JSON body. The payload carries what the consent record itself carries — nothing is collected for webhooks that is not already collected, and the visitor key is the same pseudonymous identifier your exports use.

consent.recorded

{
  "event": "consent.recorded",
  "siteId": "site_3f2a…",
  "occurredAt": "2026-09-21T10:15:04.019Z",
  "data": {
    "consentId": "consent_9b1c…",
    "decisionId": "decision_2f8a…",
    "visitorKey": "v_7d41c2e9a0b4",
    "configurationVersion": "cfg_12",
    "categories": {
      "necessary": true,
      "analytics": false,
      "marketing": false
    },
    "region": "DE",
    "source": "banner",
    "givenAt": "2026-09-21T10:15:03.884Z"
  }
}

No personal data is added

There is no IP address, no user agent and no header from the visitor’s request in a payload. If you need to join these events to a person in your own systems, the visitor key is the identifier your consent exports already use.

Verifying a delivery

Check the signature before you trust a payload. Anyone can POST JSON at your endpoint; only we can sign it with your secret.

HeaderMeaning
X-StrongPrivacy-SignatureHMAC-SHA256, hex, of {timestamp}.POST.{path}.{body} using your signing secret.
X-StrongPrivacy-TimestampMilliseconds since the epoch. Reject anything more than five minutes old.
X-StrongPrivacy-Signature-VersionCurrently 2. A future format will raise this rather than change what 2 means.
X-StrongPrivacy-DeliveryStable across retries. Store it and ignore a repeat to make your handling idempotent.
X-StrongPrivacy-EventThe event name, so you can route without parsing the body.

Verifying in Node

import { createHmac, timingSafeEqual } from 'node:crypto';

// The raw body, before any JSON parsing. Re-serialising changes the bytes
// and every signature check then fails.
export function verify(rawBody, headers, secret, path) {
  const timestamp = headers['x-strongprivacy-timestamp'];
  const signature = headers['x-strongprivacy-signature'];
  if (!timestamp || !signature) return false;

  // Reject anything older than five minutes, so a captured request cannot
  // be replayed at you later.
  if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60_000) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.POST.${path}.${rawBody}`)
    .digest('hex');

  // Constant time: comparing with === leaks the answer one byte at a time.
  return signature.length === expected.length &&
    timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

The secret is shown once

It is stored encrypted and cannot be displayed again. If you lose it, delete the endpoint and add it back to get a new one.

Retries, and being switched off

Answer 2xx once you have stored the event. Anything else is treated as a failure and retried.

  • Answer quickly. A delivery is abandoned if it takes more than ten seconds, so acknowledge first and do your own work afterwards.
  • A failure is retried with a widening gap — about one minute, then two, four, eight and sixteen — and abandoned after six attempts.
  • Redirects are not followed. Point the endpoint at its final address.
  • An endpoint that fails twenty times in a row is disabled automatically and the reason is shown beside it in the dashboard. Fix the destination, then re-enable it — that also resets the count.
  • Deliveries are queued and sent by a background job, so expect events within minutes rather than instantly. The test button delivers immediately.

Destinations must be public HTTPS

Private, internal, loopback and cloud-metadata addresses are refused when you add an endpoint and checked again before every delivery. Use a tunnel with a public address while you are developing.