Docs

Webhooks & events

AdminUpdated Sep 15, 2026

Webhooks & events

Webhooks push events to your own HTTPS endpoint as they happen, so you don't have to poll the API. Subscriptions are managed via /v1/webhooks or client.webhooks.*; this page covers the event catalogue, delivery payload, and how to verify signatures.

Event catalogue

Event

Fires when

consent.recorded

A visitor records a consent decision on a subscribed site.

scan.completed

A cookie scan finishes for a site.

scan.cookies_changed

A cookie scan finds a different cookie set than the last scan.

dsar.created

A new DSAR is opened (via the dashboard or POST /v1/dsar).

dsar.updated

A DSAR's status advances (via the dashboard or POST /v1/dsar/{id}/advance).

banner.published

A banner-library design is published to a site.

Subscribe

curl -X POST https://api.cookiemunch.net/v1/webhooks \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/cookiemunch",
    "events": ["consent.recorded", "dsar.created", "dsar.updated"]
  }'

Pass cbid to scope delivery to a single site; omit it to receive the event for every site in the org. Full CRUD reference (list/create/delete, request and response shapes): API keys, members, webhooks & usage.

Note: the create response includes the signing secret once — store it immediately. It's never returned by GET /v1/webhooks afterward.

Delivery payload

Every delivery is a POST to your url with this JSON body:

{
  "id": "evt_1b1e6f2a-...",
  "type": "consent.recorded",
  "cbid": "site_abc123",
  "createdAt": 1730332800000,
  "data": { "...": "event-specific payload" }
}

Field

Type

Description

id

string

Unique event id (evt_<uuid>), safe to use for idempotency dedup.

type

string

One of the event catalogue values above (or ping — see Testing).

cbid

string | null

The site the event is about, or null for org-level events.

createdAt

number

Epoch-ms when the event was emitted.

data

object

Event-specific payload (e.g. the DSAR record for dsar.created/dsar.updated).

Headers

Header

Description

Content-Type

Always application/json.

X-CookieMunch-Event

The event type, duplicated as a header for routing without a body parse.

X-CookieMunch-Signature

sha256=<hex hmac> — an HMAC-SHA256 of the raw request body, keyed by your subscription's secret.

Verifying signatures

Recompute the HMAC over the raw, unparsed request body using your subscription's secret, and compare it to the X-CookieMunch-Signature header with a constant-time comparison:

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

const app = express();

app.post('/hooks/cookiemunch', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.header('X-CookieMunch-Signature') ?? '';
  const expected = `sha256=${createHmac('sha256', process.env.COOKIEMUNCH_WEBHOOK_SECRET!).update(req.body).digest('hex')}`;

  const sigBuf = Buffer.from(signature);
  const expBuf = Buffer.from(expected);
  if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) {
    return res.status(401).send('invalid signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  console.log('received', event.type, event.id);
  res.status(200).end();
});

Note: use the raw body bytes for the HMAC, not a re-serialized JSON.stringify of the parsed object — even a single whitespace difference will fail the signature check. That's why the example above uses express.raw() instead of express.json() on this route.

Delivery & retries

  • Delivery is fire-and-forget and best-effort — a failed delivery never blocks or fails the API call that triggered the event.

  • A non-2xx response or network error is retried up to 3 attempts total, with linear backoff (roughly 500ms, then 1000ms between attempts).

  • After the final attempt, a failed delivery is dropped — there is no dead-letter queue. Return 2xx quickly (verify the signature, enqueue the work, respond) rather than doing slow processing inline.

  • Only active subscriptions receive events; a subscription's cbid filter (if set) must match the event's site, or it's skipped.

Testing a subscription

The dashboard's webhooks page can send a one-off test delivery to a subscription — a single attempt, no retries, delivered even if the subscription is currently paused, so you can verify your endpoint before flipping it live:

{
  "id": "evt_...",
  "type": "ping",
  "cbid": null,
  "createdAt": 1730332800000,
  "data": { "message": "This is a test delivery from Cookie Munch." }
}

It's signed exactly like a real event — verify it the same way; expect type: "ping" rather than one of the catalogue events.

SDK equivalent

const sub = await client.webhooks.create({
  url: 'https://example.com/hooks/cookiemunch',
  events: ['consent.recorded', 'dsar.created', 'dsar.updated'],
});
console.log(sub.secret); // store once

await client.webhooks.list();
await client.webhooks.delete(sub.id);

See also: banner library for the banner.published trigger, and DSAR, preferences, ROPA & vendors for the dsar.* triggers.

Was this page helpful?
Webhooks & events