Webhooks
Get a signed HTTP POST the moment content changes — so your site can rebuild or revalidate automatically.
Setup
In the dashboard open Project Settings → Webhooks, add your endpoint URL, choose the events, and save. The signing secretis shown once — store it; you'll use it to verify deliveries.
Events
| Param | Type | Description |
|---|---|---|
| entry.published | event | An entry was published, or a live entry was edited (rebuild it). |
| entry.unpublished | event | A published entry moved back to draft/archived. |
| entry.deleted | event | A published entry was deleted (remove it from the site). |
Payload
The body is intentionally small — fetch the full content from the Delivery API (it's the freshest source).
json
{
"event": "entry.published",
"projectId": "…",
"firedAt": "2026-06-29T12:00:00.000Z",
"entry": {
"id": "…",
"type": "blog_post",
"slug": "hello-world",
"status": "published",
"publishedAt": "2026-06-29T12:00:00.000Z",
"updatedAt": "2026-06-29T12:00:00.000Z"
}
}Verifying the signature
Every request carries three headers:
| Param | Type | Description |
|---|---|---|
| X-Wriven-Event | string | The event name. |
| X-Wriven-Timestamp | string | ISO time the event fired; signed, and used as a replay guard. |
| X-Wriven-Signature | string | sha256=<HMAC-SHA256 of `${timestamp}.${rawBody}` using your secret>. |
Verify against the raw request body, with a constant-time compare, and reject stale timestamps:
ts
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWriven(
rawBody: string,
headers: Record<string, string>,
secret: string,
): boolean {
const ts = headers['x-wriven-timestamp'] ?? '';
// Reject anything older than 5 minutes (replay protection).
if (Math.abs(Date.now() - Date.parse(ts)) > 5 * 60_000) return false;
const expected =
'sha256=' +
createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(headers['x-wriven-signature'] ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}// USE THE RAW BODYCompute the signature over the exact bytes received — not a re-serialized JSON object. Most frameworks let you read the raw body before parsing.
Next.js: revalidate on publish
Point the webhook at a route handler that revalidates the affected path on entry.published:
ts
// app/api/wriven-webhook/route.ts
import { revalidatePath } from 'next/cache';
import { verifyWriven } from '@/lib/wriven';
export async function POST(req: Request) {
const raw = await req.text();
const headers = Object.fromEntries(req.headers);
if (!verifyWriven(raw, headers, process.env.WRIVEN_WEBHOOK_SECRET!)) {
return new Response('Bad signature', { status: 401 });
}
const { event, entry } = JSON.parse(raw);
if (event === 'entry.published') revalidatePath(`/blog/${entry.slug}`);
return Response.json({ ok: true });
}// DELIVERYFailed deliveries are retried with backoff. The dashboard shows each webhook's last delivery status.
Back to Introduction