Kanorio

API 參考

Webhooks

接收來自 Kanorio 的已簽署事件通知,驗證請求,確保每個傳送僅處理一次,並重新整理您的前端快取。

Webhooks 會在網站發生特定事件時通知您的系統:例如發佈帖文、提交網上表單或支付訂單。Kanorio 會向訂閱該事件的每個端點發送包含 JSON 內容的 HTTPS POST 請求。

新增端點

您可以在 網站設定 › Webhooks 新增端點,或透過 Management API 使用 POST /sites/{id}/webhooks。針對每個端點,您可以選擇:

  • 事件 (Events)。 事件參考 列出了所有事件,並依產品分組,同時標註所需的權限。
  • 篩選器 (Filters)。 集合事件可限制為特定集合,表單事件則可限制為特定表單。
  • 簽章密鑰 (Signing secret)。 每個端點都有專屬的 whsec_ 密鑰,用於驗證簽章。

每個網站的端點數量取決於您的計劃。請參閱 速率限制與配額。

請求 (The request)

標頭值
Content-Typeapplication/json
X-Kanorio-Event事件鍵值,例如 collections.item.published
X-Kanorio-Delivery傳遞 ID。在重試或手動重新發佈時保持不變。
X-Kanorio-Signaturet=<unix seconds>,v1=<hex signature>
User-AgentKanorio-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,並訂閱內容事件即可。