Webhooks & events
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
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 |
|---|---|
| A visitor records a consent decision on a subscribed site. |
| A cookie scan finishes for a site. |
| A cookie scan finds a different cookie set than the last scan. |
| A new DSAR is opened (via the dashboard or |
| A DSAR's status advances (via the dashboard or |
| 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
secretonce — store it immediately. It's never returned byGET /v1/webhooksafterward.
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 |
|---|---|---|
| string | Unique event id ( |
| string | One of the event catalogue values above (or |
| string | null | The site the event is about, or |
| number | Epoch-ms when the event was emitted. |
| object | Event-specific payload (e.g. the DSAR record for |
Headers
Header | Description |
|---|---|
| Always |
| The event |
|
|
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.stringifyof the parsed object — even a single whitespace difference will fail the signature check. That's why the example above usesexpress.raw()instead ofexpress.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
2xxquickly (verify the signature, enqueue the work, respond) rather than doing slow processing inline.Only active subscriptions receive events; a subscription's
cbidfilter (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.