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

ParamTypeDescription
entry.publishedeventAn entry was published, or a live entry was edited (rebuild it).
entry.unpublishedeventA published entry moved back to draft/archived.
entry.deletedeventA 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:

ParamTypeDescription
X-Wriven-EventstringThe event name.
X-Wriven-TimestampstringISO time the event fired; signed, and used as a replay guard.
X-Wriven-Signaturestringsha256=<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