Accept and verify requests
Accept HTTPS POST requests with a JSON body. Verify X-Webhook-Signature as hex HMAC-SHA256 over the exact raw body bytes using your shared secret. Reject missing or invalid signatures. Do not calculate a signature over re-serialised JSON.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, signature, secret) {
if (!/^[a-f0-9]{64}$/i.test(signature ?? "")) return false;
const expected = createHmac("sha256", secret)
.update(rawBody).digest();
const received = Buffer.from(signature, "hex");
return expected.length === received.length
&& timingSafeEqual(expected, received);
}Handle the content events
Events are content.created, content.updated, content.published, content.unpublished and content.deleted.
For created, updated and published events, upsert published content and hide drafts. Unpublished and deleted events should remove or hide the public item. Use status, html and canonical from the payload; data contains the full source row.
Store content safely
Validate the expected tenant_id and supported content_type before storing. Upsert by content type and slug within your site. Ignore an update with an older data.updated_at than your stored record. Sanitize HTML before rendering it.
Deliveries are at least once, so your handler must tolerate duplicates. Test payloads use sample-post and data.test: true.
Acknowledge and recover
Respond with a 2xx status within 10 seconds and do expensive rebuilds asynchronously. Failed deliveries retry with backoff up to five attempts. Ten consecutive failures disable the webhook.
Fix the receiver, re-enable the webhook and run Sync all published content to catch up.