This guide is for engineers implementing a Webhook receiver. It focuses on how to verify X-Kanorio-Signature signatures, handle idempotency, and use cacheTags to trigger external frontend cache updates.
collectionKeys or formIds), or view delivery logs, please refer to Webhook Setup and Management.data Schema (SSOT): All subscribable event codes, required permissions, and data JSON examples are automatically generated by the system event registry on the Webhooks Reference page.Every Webhook notification is sent via HTTPS POST with Content-Type: application/json and the following headers:
| Header | Description |
|---|---|
X-Kanorio-Event | Event code (e.g., collections.item.published, ec.order.paid), identical to the event field at the top level of the JSON |
X-Kanorio-Delivery | Unique notification ID, identical to the id field at the top level of the JSON; remains unchanged during automatic retries and manual "resend" from the dashboard |
X-Kanorio-Signature | HMAC-SHA256 signature in the format t=timestamp,v1=signature; includes two v1= values during the 24-hour rotation period for signature keys |
User-Agent | Kanorio-Notifications/1.0 |
2xx before timeout:
Any 2xx response (e.g., 200 OK, 202 Accepted) is considered a successful delivery. If your processing (e.g., syncing external databases or triggering long-running tasks) is slow, return 200 immediately after signature verification and queueing, then process asynchronously in the background.3xx redirects:
Kanorio does not follow any 3xx redirects when sending Webhooks (receiving 301, 302, 307, or 308 is treated as a delivery failure). Ensure the URL configured in the dashboard is the final endpoint (be mindful of trailing slashes / that might trigger framework-level redirects).429 or 5xx responses trigger automatic exponential backoff retries. 410 Gone immediately disables the endpoint. If failures exceed the limit, the system automatically suspends the endpoint and notifies your team.X-Kanorio-Delivery:
Due to network retries or manual redelivery, the same notification may arrive more than once. Use X-Kanorio-Delivery (or the top-level JSON id) as a unique key for idempotency; if already processed, return 200 OK immediately.X-Kanorio-Signature)When building your receiver, always use the endpoint's "Signing Secret" (whsec_...) to verify the X-Kanorio-Signature and ensure the request originated from Kanorio and remains untampered:
X-Kanorio-Signature by commas ,. Extract the Unix timestamp after t= and all hexadecimal signature strings after v1= (there will be two v1 values during the 24-hour key rotation overlap).t with your server's current time. Reject requests with a difference greater than 5 minutes (300 seconds) (every retry recalculates t and the signature using the current time).t, a period ., and the raw, unparsed request body into {t}.{rawBody}.{t}.{rawBody} using the whsec_... secret and compare it against any v1 using crypto.timingSafeEqual.import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyKanorioWebhook(rawBody, signatureHeader, secret) { if (!signatureHeader || !secret) return false; const parts = signatureHeader.split(",").map((part) => part.split("=")); const t = parts.find(([key]) => key === "t")?.[1]; const signatures = parts .filter(([key]) => key === "v1") .map(([, value]) => value) .filter(Boolean); if (!t || signatures.length === 0) return false; const timestampSec = Number(t); if (!Number.isFinite(timestampSec) || Math.abs(Date.now() / 1000 - timestampSec) > 300) { return false; } const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const expectedBuf = Buffer.from(expected, "utf8"); return signatures.some((v1) => { const v1Buf = Buffer.from(v1, "utf8"); return v1Buf.length === expectedBuf.length && timingSafeEqual(expectedBuf, v1Buf); }); }
cacheTags UpdatesAll Webhook events share the same top-level JSON structure, with event-specific fields inside data:
{ "version": "1", "id": "3f9c2a8b1e4d7c0f9a2b3c4d5e6f7a8b", "event": "collections.item.published", "occurredAt": "2026-10-21T08:30:00.000Z", "websiteId": "cmk1examplewebsiteid", "title": "Collection item published: Sourdough Guide", "summary": "blog/sourdough-guide (published)", "data": { "collectionId": "col_01", "collectionKey": "blog", "itemId": "item_01", "slug": "sourdough-guide", "status": "published", "publicVersion": "1729500000000", "cacheTags": ["site:cmk1examplewebsiteid", "collection:blog", "item:item_01"] }, "actionUrl": "https://app.kanorio.com/data/blog" }
data.cacheTagsAll content and collection change events (collections.item.*, collections.items.bulkChanged, collections.schema.changed, content.page.*, content.site.published) include publicVersion and cacheTags in the data object (aligned with the X-Kanorio-Cache-Tags header in Content Delivery API responses):
content.page.updated includes previousSlug and adds redirects to cacheTags, allowing your external frontend to invalidate both the old path and the redirect cache.revalidateTag(tag) for each tag in payload.data.cacheTags to precisely update affected pages within seconds (see the for a full example).Please visit the Webhooks Reference page, which is automatically generated from the system event registry. It lists all subscribable events, trigger conditions, filter support, and data examples.