Webhooks
Configure webhooks to receive real-time notifications when content is created, updated, published, or deleted.
Overview
Webhooks let you notify external services when content changes in R3 CMS. When an event occurs (such as publishing a blog post), the CMS sends an HTTP POST request to your configured URL with details about the event.
Common use cases:
- Trigger a static site rebuild when content is published
- Sync content to a search index
- Send notifications to Slack or Discord
- Invalidate CDN caches
- Update external databases or services
Supported Events
| Event | Trigger |
|---|---|
entry.create | A new document is created |
entry.update | A document is saved (manual save or autosave) |
entry.delete | A document is deleted |
entry.publish | A document is published |
entry.unpublish | A published document is reverted to draft |
Webhook Payload
Every webhook delivery sends a JSON payload with the following structure:
{
"event": "entry.publish",
"timestamp": "2026-03-25T10:00:00.000Z",
"contentType": "blog_post",
"websiteId": "website-uuid",
"document": {
"id": "document-uuid",
"slug": "hello-world",
"status": "published",
"data": {
"title": "Hello World",
"body": { "type": "doc", "content": [] },
"tags": ["nextjs", "cms"]
}
}
}
| Field | Description |
|---|---|
event | The event type that triggered the webhook |
timestamp | ISO 8601 timestamp of when the event occurred |
contentType | The content type name of the affected document |
websiteId | The website ID the document belongs to |
document | The full document object including its data fields |
Signature Verification
If you configure a secret on your webhook, R3 CMS signs every payload with HMAC-SHA256. The signature is sent in the X-Webhook-Signature header.
Always verify the signature on your server to confirm the request came from R3 CMS and was not tampered with.
Verification Example (Node.js)
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhookSignature(payload, signature, secret) {
const expected = createHmac("sha256", secret)
.update(payload)
.digest("hex");
const sig = Buffer.from(signature, "utf8");
const exp = Buffer.from(expected, "utf8");
if (sig.length !== exp.length) {
return false;
}
return timingSafeEqual(sig, exp);
}
// Express / Next.js API route handler
export async function POST(request) {
const payload = await request.text();
const signature = request.headers.get("X-Webhook-Signature");
const secret = process.env.WEBHOOK_SECRET;
if (!signature || !verifyWebhookSignature(payload, signature, secret)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(payload);
console.log(`Received ${event.event} for ${event.contentType}/${event.document.slug}`);
// Handle the event
switch (event.event) {
case "entry.publish":
// Trigger rebuild, update cache, etc.
break;
case "entry.delete":
// Remove from search index, etc.
break;
}
return new Response("OK", { status: 200 });
}
Retry Behavior
If your endpoint returns a non-2xx status code or the request times out, R3 CMS retries the delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 5 seconds |
| 3rd retry | 25 seconds |
After 3 failed attempts, the delivery is marked as failed. Webhook deliveries are processed asynchronously and do not block the original mutation.
Content Type Filtering
Webhooks can be scoped to specific content types using the contentTypes field. When set, the webhook only fires for events on documents of those types. When contentTypes is null or omitted, the webhook fires for all content types.
{
"name": "Blog deploy hook",
"url": "https://your-app.com/webhooks/deploy",
"secret": "your-secret",
"events": ["entry.publish", "entry.unpublish"],
"contentTypes": ["blog_post", "blog_category"]
}
Setting Up Webhooks
Using the Admin UI
- Navigate to Webhooks in the CMS sidebar
- Click Create Webhook
- Fill in the configuration:
- Name — A descriptive label (e.g., "Deploy on publish")
- URL — The endpoint that will receive POST requests
- Secret — A shared secret for HMAC signature verification (recommended)
- Events — Which events trigger this webhook
- Content Types — Optionally limit to specific content types
- Save the webhook
Using the API
Create, update, and delete webhooks through the REST API. See the API Reference for full endpoint documentation.
curl -X POST https://cms.yourdomain.com/api/webhooks \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Rebuild site",
"url": "https://your-app.com/webhooks/rebuild",
"secret": "your-webhook-secret",
"events": ["entry.publish", "entry.unpublish", "entry.delete"],
"contentTypes": null
}'
Best Practices
- Always set a secret and verify signatures to prevent unauthorized requests to your endpoint.
- Respond quickly with a 200 status code. Perform heavy processing asynchronously after acknowledging the webhook.
- Handle duplicate deliveries gracefully. In rare cases, the same event may be delivered more than once. Use the
document.idandtimestampto deduplicate. - Monitor failures in the CMS admin panel. If a webhook consistently fails, check that your endpoint is reachable and returning 2xx responses.