網站上發生的事,Kanorio 可以即時通知您的系統:買家付款、包裹出貨或送達、訪客送出表單、寄件網域失效、寄送額度用完、流量突破里程碑。接上 Zapier、Make、n8n 或您自己的程式後,就能自動寫進試算表、轉發到團隊的聊天群組、建立客服工單,或同步到您的 ERP、CRM。
設定位置在網站主控台的技術設定,「串接」分頁。Webhook 是商務版功能,只有網站的擁有者與管理員可以新增或管理;其他成員看不到網址、金鑰與推送紀錄。
每個網站最多可以新增 5 個 Webhook。
每個端點展開後都有「訂閱事件」:
目前有這些事件(欄位細節見文末「事件參考」):
| 事件 | 什麼時候送 | 事件代碼 |
|---|---|---|
| 訂單付款 | 買家完成付款時 | ec.order.paid |
| 貨態更新 | 包裹出貨、到店、送達或配送異常時 | ec.shipment.statusChanged |
| 表單回覆 | 訪客送出表單時,包含完整的題目與答案 | customers.form.submitted |
| 寄件網域失效 | 您的寄件網域驗證失敗時 | customers.sendingDomain.degraded |
| 寄送額度提醒 | 額度用完、自動加購、加購達上限或扣款失敗時 | customers.messagingQuota.alert |
| 流量里程碑 | 網站累計瀏覽量突破 100、500 時 | site.analytics.milestone |
| 串接暫停 | 某個 Webhook 因連續失敗被暫停時(不會送到被暫停的那個端點) | customers.teamDestination.disabled |
| 測試事件 | 按「傳送測試」時 | team.test |
要不要送 Webhook 只看這裡的訂閱。自動通知的各情境面板會顯示「N 個端點已訂閱」或「尚未連接」,但開關在這裡,不在自動通知。
Webhook 不會取代寄給您的 Email。加了 Webhook 之後,原本會寄給擁有者的通知信照樣會寄出,例如新訂單與表單回覆;貨態更新的團隊 Email 預設就是關閉的。要開或關哪些團隊 Email,請在「自動通知」設定。
每個端點展開後都看得到「最近推送」:事件、結果、HTTP 狀態與時間;再展開每一筆可以看嘗試次數、耗時、下次重試時間與錯誤原因。7 天內的通知可以「查看內容」看送出的資料,也可以按「重送」再送一次;重送用同一個通知編號,接收端可以用它判斷是否重複。
「傳送測試」不會計入失敗次數,排查時可以放心多按幾次。
Kanorio 從固定的 IP 送出 Webhook。若您的接收端只允許白名單來源,請把以下三個 IPv4 全部加入(首次送出、測試與手動重送來自前兩個,自動重試來自第三個):
| 用途 | IP |
|---|---|
| 首次送出、測試、手動重送 | <MAIN_EGRESS_IP_1> |
| 首次送出、測試、手動重送 | <MAIN_EGRESS_IP_2> |
| 自動重試 | 168.144.105.51 |
白名單只是額外的防護,不能取代簽章驗證:請一律驗證 X-Kanorio-Signature(見下方「給工程師」)。IP 若需變更,我們會先在這裡公告,並保留新舊 IP 並行一段時間再移除舊的。
不會。Webhook 不經 Kanorio 寄信,不計入 Email 寄送額度。
端點會暫停推送,設定與金鑰都會保留,列表上標示「超出方案」。升級回商務版後立即恢復。
接收端回應太慢或出錯時,Kanorio 會重試;您也可能手動重送。請用 X-Kanorio-Delivery(或 JSON 的 id)判斷是否已處理過。
目前可以先透過 Zapier 或 Make 轉發:用 Webhook 接收 Kanorio 的通知,再送到 Slack 頻道或 LINE。
Webhook 會把訂單與表單答案送到 Kanorio 以外的系統,網址與簽章金鑰只有擁有者與管理員看得到。其他成員只看得到端點的名稱與狀態。
沒有獨立的沙盒。用「傳送測試」確認連線,再用「查看內容」看真實事件送出的資料;開發階段可以先把端點指到 webhook.site 這類接收服務觀察格式。
每則通知是一個 POST,Content-Type: application/json,附以下 header:
| Header | 內容 |
|---|---|
X-Kanorio-Event | 事件代碼,與 JSON 的 event 相同 |
X-Kanorio-Delivery | 通知編號,與 JSON 的 id 相同;重試與重送時不變 |
X-Kanorio-Signature | 簽章,格式 t=時間戳,v1=簽章;金鑰輪替期間會有兩個 v1 |
User-Agent | Kanorio-Notifications/1.0 |
接收端回應任何 2xx 即視為成功,回應內容不會被讀取。請在 8 秒內回應;需要較長處理時間時,先回 200 再非同步處理。Kanorio 不跟隨重導,3xx 會被視為失敗。
所有事件的 JSON 都是同一個外殼,接收端只要設定一次:
{ "version": "1", "id": "3f9c2a…", "event": "ec.order.paid", "occurredAt": "2026-09-29T12:00:00.000Z", "websiteId": "cmk…", "title": "新訂單 #1024,NT$1,280", "summary": "「我的商店」收到一筆已付款的訂單。", "fields": [{ "label": "訂單編號", "value": "#1024" }], "data": { "…": "依事件而異,見下方事件參考" }, "actionUrl": "https://app.kanorio.com/store/transactions?tx=…" }
| 欄位 | 型別 | 說明 |
|---|---|---|
version | string | 目前是 "1"。只新增欄位不會改版本號;移除或改名既有欄位才會升版,並在這篇文章公告 |
id | string | 通知編號(32 碼),與 X-Kanorio-Delivery 相同。重試與重送不變,請用它去重 |
event | string | 事件代碼,見上方事件表 |
occurredAt | string | 事件發生時間,ISO 8601(UTC) |
websiteId | string | 網站 ID。同一個端點只會收到建立它的那個網站的事件 |
title、summary | string | 依網站語系排好的一行標題與摘要,可直接轉發到聊天群組 |
fields | array | { label, value } 的陣列,人讀用的細節(訂單編號、金額、表單每一題與答案…)。標籤文字隨網站語系變化,程式判斷請用 data |
data | object | 事件的原始資料,不隨語系改變。每個事件的欄位見下方 |
actionUrl | string | null | 回到 Kanorio 後台查看這筆資料的連結 |
以下是每個事件的 data 欄位。「可空」表示該欄位可能是 null。
ec.order.paid 訂單付款買家完成付款、訂單成立時送出;每筆訂單一次。
| 欄位 | 型別 | 可空 | 說明 |
|---|---|---|---|
orderId | string | 訂單 ID,與後台網址 ?tx= 相同 | |
orderNumber | number | ✓ | 人類可讀的訂單編號(例如 1024);極早期的訂單可能沒有 |
amount | number | 買家支付總額(商品+運費),以該幣別的分為單位(ISO 4217 minor unit):NT$1,280 是 128000、US$12.80 是 1280、日圓沒有小數則 ¥1,280 是 1280。換算成顯示金額請除以 10 的「小數位數」次方 | |
currency | string | ISO 4217 大寫,例如 TWD、USD | |
itemCount | number | 品項數量 | |
customerName | string | ✓ | 買家姓名 |
customerEmail | string | ✓ | 買家 Email |
{ "event": "ec.order.paid", "data": { "orderId": "cmk7…", "orderNumber": 1024, "amount": 128000, "currency": "TWD", "itemCount": 2, "customerName": "王小明", "customerEmail": "ming@example.com" } }
ec.shipment.statusChanged 貨態更新出貨單進入出貨、到店、送達或配送異常時送出;同一張出貨單的每個貨態各一次。
| 欄位 | 型別 | 可空 | 說明 |
|---|---|---|---|
orderId | string | 訂單 ID | |
orderNumber | number | ✓ | 訂單編號 |
shipmentId | string | 出貨單 ID;一筆訂單可能有多張出貨單 | |
status | string | IN_TRANSIT(已出貨)、AT_STORE(已到店)、DELIVERED(已送達)、EXCEPTION(配送異常) | |
carrier | string | 物流服務代碼,例如 PAYUNI_LOGISTICS、MANUAL(賣家自寄) | |
trackingNumber | string | ✓ | 追蹤號碼/託運單號 |
storeName | string | ✓ | 超商取貨的門市名稱;宅配為 null |
{ "event": "ec.shipment.statusChanged", "data": { "orderId": "cmk7…", "orderNumber": 1024, "shipmentId": "cmk8…", "status": "AT_STORE", "carrier": "PAYUNI_LOGISTICS", "trackingNumber": "T1234567890", "storeName": "7-ELEVEN 台北門市" } }
customers.form.submitted 表單回覆訪客送出表單且通過垃圾判定時送出;每筆回覆一次。
| 欄位 | 型別 | 可空 | 說明 |
|---|---|---|---|
formId | string | 表單 ID | |
formName | string | 表單名稱 | |
submissionId | string | 回覆 ID | |
submitter | object | { name, email, phone },各自可為 null;由表單裡的姓名、Email、電話題自動辨識 | |
answers | array | 依題目順序,每題一個物件(見下表) |
answers[] 的每一項:
| 欄位 | 型別 | 說明 |
|---|---|---|
fieldId | string | 題目 ID,同一張表單內固定;請用它而不是 label 對應欄位 |
type | string | 題型:short_text、long_text、email、phone、name、choice、date、time、rating、file |
label | string | 題目文字 |
value | string | string[] | number | null | 原始答案。單選是字串、多選是字串陣列、評分是數字、日期時間是字串(YYYY-MM-DD、HH:mm)、檔案題是檔案 ID(一個或多個);沒填為 null |
display | string | 人讀字串,與 fields 裡的相同;檔案題顯示檔名 |
{ "event": "customers.form.submitted", "data": { "formId": "cmk9…", "formName": "聯絡我們", "submissionId": "cmka…", "submitter": { "name": "王小明", "email": "ming@example.com", "phone": null }, "answers": [ { "fieldId": "f_name", "type": "name", "label": "姓名", "value": "王小明", "display": "王小明" }, { "fieldId": "f_email", "type": "email", "label": "Email", "value": "ming@example.com", "display": "ming@example.com" }, { "fieldId": "f_topic", "type": "choice", "label": "想了解", "value": ["報價", "合作"], "display": "報價、合作" }, { "fieldId": "f_msg", "type": "long_text", "label": "留言", "value": "想詢問企業方案", "display": "想詢問企業方案" }, { "fieldId": "f_file", "type": "file", "label": "附件", "value": ["cmkb…"], "display": "需求書.pdf" } ] } }
customers.sendingDomain.degraded 寄件網域失效已驗證的自有寄件網域驗證失效時送出;同一個網域每天最多一次。
| 欄位 | 型別 | 說明 |
|---|---|---|
domain | string | 失效的寄件網域 |
{ "event": "customers.sendingDomain.degraded", "data": { "domain": "mail.example.com" } }
customers.messagingQuota.alert 寄送額度提醒寄送額度狀態變化時送出。
| 欄位 | 型別 | 說明 |
|---|---|---|
reason | string | exhausted(本期額度用完)、auto_upgraded(已自動加購一級)、auto_upgrade_limit(本期自動加購達上限)、payment_failed(加購費用扣款失敗) |
quota | number | 本期額度(封) |
levels | number | 目前的加購級數 |
periodStart | string | 本期起算時間,ISO 8601 |
{ "event": "customers.messagingQuota.alert", "data": { "reason": "exhausted", "quota": 1000, "levels": 0, "periodStart": "2026-09-01T00:00:00.000Z" } }
site.analytics.milestone 流量里程碑網站累計瀏覽量突破里程碑時送出,每個里程碑一次。
| 欄位 | 型別 | 說明 |
|---|---|---|
milestone | number | 100 或 500 |
{ "event": "site.analytics.milestone", "data": { "milestone": 500 } }
customers.teamDestination.disabled 串接暫停某個 Webhook 端點被自動暫停時送出(連續失敗達門檻或接收端回 410)。只會送到其他仍正常的端點。
| 欄位 | 型別 | 說明 |
|---|---|---|
destinationId | string | 被暫停的端點 ID |
failures | number | 連續失敗次數 |
lastError | string | 最後一次的錯誤,例如 HTTP 503、逾時 |
{ "event": "customers.teamDestination.disabled", "data": { "destinationId": "cmkc…", "failures": 3, "lastError": "HTTP 503" } }
team.test 測試事件按「傳送測試」時送出,只送一次、不重試、不寫入推送紀錄。
{ "event": "team.test", "data": { "test": true } }
驗證方式:
X-Kanorio-Signature 取出 t 與所有 v1。t、一個英文句點與原始的請求內容(未經解析、重新序列化的 raw body)接成一串,例如 1790000000.{"version":"1",…}。v1 比對,相同才接受。Node.js 範例:
import { createHmac, timingSafeEqual } from "node:crypto"; function verify(rawBody, header, secret) { 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 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); return signatures.some( (v1) => v1.length === expected.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) ); }
Zapier、Make、n8n 這類服務通常沒有地方寫驗證邏輯;它們的接收網址本身含隨機路徑,一般不另外驗簽。
簽章金鑰之後可以在串接列表展開該端點,按「顯示」再看一次。懷疑金鑰外流時按「產生新金鑰」:
v1),任一個相符即可。