API 參考
Webhooks
接收來自 Kanorio 的已簽署事件通知,驗證請求,確保每個傳送僅處理一次,並重新整理您的前端快取。
Webhooks 會在網站發生特定事件時通知您的系統:例如發佈帖文、提交網上表單或支付訂單。Kanorio 會向訂閱該事件的每個端點發送包含 JSON 內容的 HTTPS POST 請求。
新增端點
您可以在 網站設定 › Webhooks 新增端點,或透過 Management API 使用 POST /sites/{id}/webhooks。針對每個端點,您可以選擇:
- 事件 (Events)。 事件參考 列出了所有事件,並依產品分組,同時標註所需的權限。
- 篩選器 (Filters)。 集合事件可限制為特定集合,表單事件則可限制為特定表單。
- 簽章密鑰 (Signing secret)。 每個端點都有專屬的
whsec_密鑰,用於驗證簽章。
每個網站的端點數量取決於您的計劃。請參閱 速率限制與配額。
請求 (The request)
| 標頭 | 值 |
|---|---|
Content-Type | application/json |
X-Kanorio-Event | 事件鍵值,例如 collections.item.published |
X-Kanorio-Delivery | 傳遞 ID。在重試或手動重新發佈時保持不變。 |
X-Kanorio-Signature | t=<unix seconds>,v1=<hex signature> |
User-Agent | Kanorio-Notifications/1.0 |
每個事件都使用相同的封裝格式。事件特定的欄位位於 data 中。
{ "version": "1", "id": "3f9c2a8b1e4d7c0f9a2b3c4d5e6f7a8b", "event": "collections.item.published", "occurredAt": "2026-11-02T08:30:00.000Z", "websiteId": "ws_example123", "title": "Collection item published: Sourdough Guide", "summary": "blog/sourdough-guide (published)", "data": { "collectionKey": "blog", "itemId": "item_01", "slug": "sourdough-guide", "status": "published", "publicVersion": "1729500000000", "cacheTags": ["collection:blog", "item:item_01", "seo", "search"] }, "actionUrl": "https://app.kanorio.com/data/blog", "operationId": null, "principal": { "kind": "USER", "id": "usr_example01" } }
| 欄位 | 備註 |
|---|---|
id | 傳遞 ID,等於 X-Kanorio-Delivery。請使用此 ID 進行去重。 |
event | 等於 X-Kanorio-Event。 |
data | 事件特定內容。內容事件包含 locale、publicVersion 及 cacheTags。 |
operationId | 針對屬於追蹤操作的事件進行設定,例如表單提交、付款、退款及存取授權。請使用 GET /operations/{id} 查詢。否則為 null。 |
principal | 發起變更的對象:USER、SERVICE_ACCOUNT、CUSTOMER、SYSTEM 或 VISITOR。未知時為 null;切勿將 null 視為上述任何一種。 |
驗證簽章
使用端點的密鑰計算 {t}.{rawBody} 的 HMAC-SHA256,並以恆定時間 (constant time) 與每個 v1 值進行比較。若請求中的 t 與您的系統時間相差超過五分鐘,請拒絕該請求。請務必使用原始請求內容 (raw request body),在進行任何 JSON 解析之前進行驗證。
當您輪替簽章密鑰時,Kanorio 會在 24 hours 期間同時使用新舊密鑰進行簽署,因此標頭會包含兩個 v1 值。只要其中一個相符,即可接受請求。
SDK 會為您處理此過程:
import { verifyWebhookSignature } from "@kanorio/sdk"; export async function POST(request: Request) { const rawBody = await request.text(); const valid = await verifyWebhookSignature({ rawBody, signatureHeader: request.headers.get("x-kanorio-signature"), signingSecret: process.env.KANORIO_WEBHOOK_SECRET!, }); if (!valid) return new Response("Invalid signature", { status: 401 }); const event = JSON.parse(rawBody); // handle event.event and event.data return new Response(null, { status: 200 }); }
若不使用 SDK,在 Node.js 中:
import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyKanorioWebhook(rawBody, header, secret) { if (!header || !secret) return false; const parts = header.split(",").map((part) => part.split("=")); const t = parts.find(([key]) => key === "t")?.[1]; const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value); if (!t || signatures.length === 0) return false; if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")); return signatures.some((v1) => { const actual = Buffer.from(v1); return actual.length === expected.length && timingSafeEqual(actual, expected); }); }
回應與重試
- 請在 8 seconds 內回傳任何
2xx狀態碼。若處理時間較長,請先將事件放入佇列並立即回應。 - Kanorio 不會跟隨重新導向。
3xx會被視為失敗,因此請註冊最終的 URL,包含您的框架所預期的結尾斜線。 - 超時、網絡錯誤、
429與5xx錯誤將在 1m, 5m, 30m, 2h, 8h, 1d, 1d 後重試 7 次。每次重試都會使用新的時間戳記重新簽署。 410 Gone會立即停用該端點。若連續失敗 3 次,端點將會暫停,並通知網站團隊。- 傳送內容會保留 7 days,因此您可以在此期間從後台重新發送傳送紀錄。
確保每個傳送僅處理一次
重試與手動重新發送可能會導致同一個事件被傳送多次。請儲存您已處理的 X-Kanorio-Delivery ID,當您再次看到相同的 ID 時,請直接回傳 200 而無需重複執行作業。
重新整理前端快取
每個 Content Delivery API 的回應都會在 X-Kanorio-Cache-Tags 中列出其依賴項目,而每個內容事件也會在 data.cacheTags 中列出變更項目,並使用相同的標籤。請使用回應標籤標記您框架的快取,並重新驗證事件標籤,這樣所有使用該變更內容的頁面將在幾秒鐘內重新整理,而其他內容則保持快取狀態。
| 標籤 | 回應範圍 | 觸發事件 |
|---|---|---|
site:<id> | 網站的每項回應 | content.site.published, localization.locale.*(語言切換器及 hreflang 存在於每個頁面) |
locale:<code> | 次要語系的所有回應 | localization.job.completed |
page:<id>, item:<id>, product:<id>, form:<id> | 該記錄在所有語系中的回應,以及嵌入該記錄的回應(麵包屑導航祖先、展開的參照) | 記錄本身的事件,因此主要語系的變更會重新整理所有語系(未翻譯的欄位會回退至主要文字) |
page:<id>@<code>, item:<id>@<code>, … | 僅該語系的回應 | content.translation.published / deleted:僅重新整理該語系 |
pages, collection:<key>, products (及 @<code>) | 僅列表回應 | 該列表中記錄的事件 |
collection:<key>:items | 該集合的每項單一項目回應 | collections.schema.changed,以及過大而無法逐項列出的批次變更 |
globals, header:<key>, footer | 頁首及頁尾回應 | content.site.published,全域區段翻譯 |
search, seo, redirects | 網站搜尋、SEO 檔案 (/seo/*)、重新導向表 | 變更這些項目的內容事件;content.page.updated 會在頁面重新命名時新增 redirects |
標籤使用語言代碼,而非 URL 前綴(擁有者可能會變更前綴)。revalidateTag 會比對完整字串,因此事件總是列出具體的標籤。單筆記錄事件不會攜帶 site:<id>:如果您僅使用網站標籤來標記快取,請訂閱 content.site.published,或者更好的做法是,使用回應本身的標籤來標記每次抓取。
在 Next.js 中,createRevalidateHandler 可在單行程式碼內完成簽章驗證與標籤重新驗證。它會為每個標籤加上 kanorio: 前綴,以避免 Kanorio 的標籤與您的標籤衝突;請透過 frameworkCacheTags 或 cacheTag 建構器來標記您的抓取內容,確保雙方匹配(傳入 tagPrefix: "" 即可使用原始標籤):
// app/api/revalidate/route.ts import { revalidateTag } from "next/cache"; import { createRevalidateHandler } from "@kanorio/sdk"; export const POST = createRevalidateHandler({ signingSecret: process.env.KANORIO_WEBHOOK_SECRET!, revalidateTag: (tag) => revalidateTag(tag), });
對於靜態建置的網站,請將 Webhook 指向您主機的部署 Hook,並訂閱內容事件即可。