Skip to Content
Webhook總覽

Webhook 總覽

Webhook 是權威訊號。瀏覽器回呼 (onSuccess) 與儀表板畫面僅是 輔助資訊;Webhook 才是真相之源。

投遞保證

  • 至少一次。 若你的伺服器未在逾時內回應 2xx,單一事件最多會被 投遞 6 次。處理函式請保持冪等 — 以 X-Delivery 去重。
  • 每個 HTTP 請求僅一個事件。 不做批次。
  • 每個端點獨立。 如果你註冊了多個端點,各自擁有獨立的投遞與 重試軌道。某個慢的商家 URL 不會拖累其他端點 — 每台主機都有自己的 斷路器。
  • 已簽章。 每個酬載都帶 X-Signature 標頭(於密鑰輪替後的 24 小時 視窗期內,還會附帶 X-Signature-Prev)。處理 body 前請先驗證。 詳見簽章驗證

可訂閱的事件類型

事件觸發條件
payment.settled鏈上轉帳達到該鏈的確認數。用此事件標記訂單為已付款。
payment.failed法幣付款被 provider 明確拒絕(目前:Stripe webhook 通報失敗)。不會因為加密貨幣超時而觸發 — 那些情境會以 checkout.expired 呈現;而短付的加密貨幣付款會以 payment.underpaid 呈現。
payment.underpaid資金已抵達但不足訂單總額(典型範例:穩定幣轉帳手續費從金額中扣除)。
payment.overpaid資金超過訂單總額。多出的部分被記錄,但不會自動退款。
order.created新訂單建立 — 透過你的 B2B API 呼叫或結帳會話轉換。
order.canceled訂單轉為取消。酬載中的 data.reason 區分手動取消與 payment_timeout(worker 清掃的過期未付訂單)。
order.resolved一個 PARTIAL_PAID 訂單被解析為 PAID — 商家接受短缺。
order.reopened先前自動取消(canceled_reason=payment_timeout)的訂單被商家重新開啟。
checkout.created買家開啟了訂單的結帳。
checkout.completed買家端流程完成(不代表鏈上結算 — 那是 payment.settled 的事)。
checkout.expired買家放棄,會話 TTL 已過。
payment.refund.requested退款紀錄被建立 — 來自商家發起的 API 呼叫,或客戶提交的退款申請表單。
payment.refund.approved待審退款通過你的核准流程。
payment.refund.rejected待審退款被駁回。
payment.refund.executed退款的鏈上轉帳已確認,紀錄進入終態 executed
refund_request.createdRefund-request token 被鑄造。data.sourceb2b / dashboard / renewal。訂閱與否為選擇性 — 對於追蹤每個訂單目前哪個 token 是有效的稽核管線很有用。
refund_request.renewal_requested買家在 token 過期後點了「申請新連結」。強烈建議訂閱 — 這是商家收到續期 widget 有新項目需要處理的訊號。
refund_request.renewed續期通過核准,新的 token 取代舊的。data.old_token / data.new_token 形成稽核鏈。
refund_request.canceled商家從儀表板把一個 token 翻為 CANCELED(例如駁回續期請求、終止仍有效的連結)。冪等 — 只有第一次轉換會發出。data.reason 是商家選填的備註。

儀表板從 GET /v1/webhooks/event-types 取得此列表,所以端點建立與 編輯表單永遠匹配平台實際發出的事件。訂閱平台不發出的事件會在建立 時被拒絕,並附帶清楚的錯誤訊息。

測試事件無法訂閱。 儀表板上的逐端點 Send Test 按鈕會把一個 webhook.test.ping 信封同步 POST 到該端點(繞過重試管線);而舊版的 商家層級「發送測試事件」管道則無論過濾器為何,都會把 webhook.test 信封散發到每個活躍端點。兩者都不會出現在上面的目錄中 — 你是因為註冊了 端點才會收到它們,而不是靠訂閱。

只訂閱你會處理的事件。每個端點有獨立的事件過濾器;萬用字元 "*" 代表「所有事件,包含未來新增的」。訂閱較少事件可讓處理函式更整潔, 並且減少我們需要重試的範圍。

酬載 + 標頭

HTTP body 直接是事件專屬的 data 物件。沒有 Stripe 風格的外層 信封 — 事件類型、投遞 ID、發出時間等欄位改放在標頭裡。對於 payment.settled,body 看起來像這樣:

{ "receipt_id": "rcp_…", "order_id": "ord_…", "payment_intent_id": "pin_…", "checkout_session_id": "cst_…", "merchant_id": "mer_…", "customer_id": "cus_…", "total": "49.00", "currency": "USD", "payment_method": "crypto", "token": "USDC", "network": "polygon", "tx_hash": "0x…", "deposit_address": "0x…", "treasury_address": "0x…", "amount_received": "49.00", "confirmations": 5, "metadata": { /* 每事件 */ } }

其他事件帶各自的欄位集合 — 在逐事件文件發佈之前,規範形狀請見 payment-service/internal/domain/events.go 的 publisher struct。 欄位名稱穩定(lower snake_case);鏈上 tx hash 永遠是 tx_hash (不是 transaction_hash)。

入站請求的標頭

Content-Type: application/json X-Event: payment.settled X-Delivery: 7c9e6679-7425-40de-944b-e07fc1f90ae7 Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7 X-Timestamp: 1729536000 X-Signature: sha256=9a8b7c… X-Signature-Prev: sha256=fa31b2… (僅在輪替寬限視窗期內出現)
標頭內容
X-Event事件類型(例如 payment.settled)。如想跳過 JSON 解析,可在代理層以此路由。
X-DeliveryUUID,識別一筆投遞列。在同一 (event, endpoint) pair 的所有重試間穩定 — 請以它作為冪等鍵。
Idempotency-KeyX-Delivery 對應(同值)。每次投遞都會帶 — 沿用 Stripe / GitHub 的慣例。
X-Timestamp嘗試送出時的 Unix 秒。簽入酬載,使被捕獲的 (body, X-Signature) 對無法被無限重放 — 對超出容差視窗的投遞請拒絕。
X-SignatureHMAC-SHA256(secret, X-Timestamp + "." + raw_body)sha256=<hex>。詳見簽章驗證
X-Signature-Prev相同演算法但使用前一個金鑰。僅在輪替後的 24 小時視窗期內出現 — 讓使用任一金鑰的驗證器在切換期間都能繼續接受投遞。視窗關閉後此標頭便不再發送。

重試時程

若你的端點未在逾時內回應 2xx,我們依下列時程重試(時間戳相對首次 嘗試):

嘗試延遲累計
10s0s
2+1 分1m
3+5 分6m
4+15 分21m
5+1 小時1h 21m
6+6 小時7h 21m

第 6 次嘗試失敗後,投遞進入死信,商家帳號信箱會收到通知。死信 事件可從儀表板的 Developers → Webhooks → Delivery history 面板 重放,或直接透過 POST /v1/webhooks/deliveries/:id/replay 觸發。 每次重放會建立一筆新的投遞列、擁有自己的 X-Delivery — 稽核鏈 透過 parent_delivery_id 連回原始事件,讓 replay 的重試不會掩蓋 原始事件。

註冊端點

商家儀表板 中:

  1. Developers → Webhooks+ Add endpoint
  2. 貼上你的 URL — 僅限 https://…(純 HTTP 會被拒絕;建立表單也會 阻擋 localhost、私有 IP 範圍、以及攜帶使用者資訊的 URL)
  3. 選擇要訂閱的事件(或 * 代表全部)
  4. 選擇環境 — testlive(各自擁有獨立金鑰,二者永不交叉)
  5. 儲存 → 儀表板僅顯示一次簽章金鑰(whsec_…)。請保存到伺服端; 下面兩個功能會用到它。

每個商家每個環境最多可註冊 10 個端點(例如:一個用於正式履約, 一個用於預備鏡像,一個用於 Slack 通知)。每個端點維護獨立的重試 狀態、金鑰以及主機級斷路器。

每個端點的生命週期操作

每張端點卡片的 ⋮ 選單提供:

  • Edit — 變更 URL、描述或訂閱列表。新 URL 會以與建立時相同的 https:///SSRF 規則重新驗證。
  • Send Test — 以你目前的金鑰同步 POST 一個 webhook.test.ping 信封。儀表板顯示 HTTP 狀態、延遲與你回應的前 512 位元組。繞過 RMQ 管線,結果立刻返回。
  • Rotate Secret — 產生新金鑰。前一個金鑰仍維持 24 小時 有效 (期間投遞同時帶 X-SignatureX-Signature-Prev,讓使用任一 金鑰的驗證器在你重新部署期間都能繼續接受事件)。
  • Reveal Secret — 重新顯示目前金鑰。受新的 2FA 驗證控管並記錄 到稽核日誌;僅在你遺失副本且無法接受輪替時使用。
  • Enable / Disable — 不遺失投遞歷史的情況下切換 is_active。 停用的端點仍保留在儀表板,但不再接收新投遞。
  • Delete — 永久刪除。若可能再次啟用,請使用 Disable。

處理函式小技巧

  1. 儘速回應 2xx。 在做重活前先以 200 OK 確認 — 把履約丟到 背景佇列。每次嘗試的逾時為 10 秒;回應保持超過此時間會觸發 重試。此逾時為平台端設定,商家無法調整 — 若你的 handler 確實 需要更多時間,請聯絡 support。
  2. X-Delivery 去重(或同值的 Idempotency-Key)。即使你 回應了 2xx,上游代理可能斷線並觸發重試;投遞 ID 在同一筆投遞 列的每次重試之間穩定,因此是正確的鍵。
  3. 容忍未知事件類型。 可能會出現新事件;請回 200 + no-op, 而非 4xx,否則重試佇列會被塞滿。
  4. 在業務邏輯旁邊記錄 X-Delivery 出問題時,這就是我們這邊 與你那邊的串接鍵。

下一步