Webhooks
Webhooks let an external system react to things that happen in your space. Each webhook subscribes to one or more events (per action, grouped by module) and receives an HTTP POST whenever a matching event fires.
Manage them in Settings → Developers → Webhooks.
Events
Events are grouped by module, the same way permissions are grouped on the roles page. Tick the exact actions you care about.
| Module | Event key | Fires when |
|---|---|---|
| Rendering | render.completed | A page finishes rendering (POST /api/render or GET /api/render/:pageId). |
| Brands | brand.created | A brand is created. |
| Brands | brand.updated | A brand's tokens or details are updated. |
| Brands | brand.deleted | A brand is deleted. |
| Content | entry.created | A content entry is created in a collection. |
| Content | entry.updated | A content entry is updated. |
| Content | entry.published | A content entry is published. |
| Content | entry.deleted | A content entry is deleted. |
| Content | entryType.created | A content type (collection) is created. |
| Content | entryType.updated | A content type's fields or settings are updated. |
| Content | entryType.deleted | A content type is deleted. |
Delivery payload
Every delivery is a POST with a JSON body:
{
"id": "b7c1e0f2-…", // unique per delivery
"event": "brand.updated", // the event key
"timestamp": "2026-07-08T10:00:00.000Z",
"spaceId": "665f…",
"data": { "doc": { "_id": "…", "name": "…" } }
}
The data shape depends on the event — the affected document for create/update, an { id } for delete, and render metadata (pageId, format, width, height, renderMs, file, …) for render.completed.
Headers we set
| Header | Description |
|---|---|
Content-Type | Always application/json. |
X-Webhook-Id | The webhook's id. |
X-Webhook-Event | The event key (e.g. brand.updated). |
X-Webhook-Delivery | The unique delivery id (matches id in the body). |
X-Webhook-Timestamp | ISO timestamp the event was emitted. |
X-Webhook-Signature | sha256=<hex> HMAC of the raw body — only when a secret is set. |
Verifying the signature
Set a Secret on the webhook and every delivery is signed. Recompute the HMAC over the raw request body with your secret and compare it (constant-time) to the X-Webhook-Signature header.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(rawBody: string, header: string, secret: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
const a = Buffer.from(header)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}
Reject any request whose signature does not match.
Custom headers
Add any number of custom headers to a webhook — for example a bearer token or a shared secret your receiver checks, or a routing hint. They are sent on every delivery. Reserved headers (Content-Type, User-Agent, and the X-Webhook-* headers above) cannot be overridden.
Delivery behaviour
- Deliveries are best-effort and fire-and-forget: a failing or slow receiver never fails the render/save that triggered it.
- Each request times out after 10 seconds.
- Only public URLs are allowed. The Post URL must be a public
httpsaddress (SSRF protection): private, loopback, link-local, and cloud-metadata targets are rejected at save time and re-checked at delivery time, and redirects are not followed.
Delivery history & resend
Every attempt is logged. Open a webhook's history (the clock icon in Settings → Developers) to see its recent deliveries with:
- Status —
success,failed, orblocked(rejected by the URL guard). - Response code returned by your endpoint, and the duration of the request.
- When it fired, and the error message on failures (hover the timestamp).
Use Resend to replay a past delivery — it sends the same event and data again with a fresh delivery id and signature, so you can retry after fixing your endpoint. Delivery records are retained for 3 months, then removed automatically. Deleting a webhook also removes its history.
- Only enabled webhooks whose triggers include the event are delivered to.
- Deliveries run out of the request's critical path, so the API response is not delayed.
Your endpoint should respond quickly with a 2xx and do any heavy work asynchronously.
Summary
| Aspect | Description |
|---|---|
| Direction | Outbound — you supply the URL. |
| Trigger | Any subscribed event in your space. |
| Auth | HMAC-SHA256 signature (optional secret) + optional custom headers. |
| Retries | None yet; treat delivery as at-most-once. |
| Impact on API | None; delivery is deferred and best-effort. |
