Webhooks 詳解:簽名載荷與重試策略
你剛剛建立了一個 Webhook 端點來接收第三方服務的事件。在測試環境中一切正常,但在生產環境中你發現有些事件從未送達,更糟的是,你收到重複事件導致資料不一致。這些是典型的 Webhook 陷阱,源於兩個關鍵層面:載荷真實性和傳遞可靠性。
Webhook 是由事件觸發的 HTTP 回呼,即時將資料推送到你的應用程式。與輪詢不同,它們高效且近乎即時。但它們也帶來挑戰:你如何知道請求確實來自預期的發送方,以及當接收方暫時無法運作時如何處理失敗?本文深入探討簽名載荷和重試機制,為你提供建立韌性 Webhook 整合的實用模式。
為什麼 Webhook 需要簽名
Webhook 端點是公開可存取的 URL。如果沒有驗證,任何人都可以向你的端點發送偽造請求,可能觸發未授權的操作或資料損毀。簽名載荷透過為每個請求添加加密簽名來解決此問題,讓你能驗證發送方的身份並確保載荷未被篡改。
大多數供應商使用 HMAC(基於雜湊的訊息驗證碼)搭配共享密鑰。發送方使用密鑰計算載荷的雜湊值並將其包含在標頭中。你在自己這邊重新計算雜湊值並進行比較。如果匹配,則請求是合法的。
實作 HMAC 驗證
讓我們逐步說明 Node.js 中的典型實作。假設供應商在 X-Signature 標頭中發送簽名,該簽名是使用你的密鑰對原始請求主體計算的 HMAC-SHA256。
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body parser to preserve exact payload
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const expected = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(req.body);
// ... handle event
res.status(200).send('OK');
});
重點:
- 驗證時始終使用原始請求主體,而非解析後的 JSON。解析可能會改變空白字元或鍵的順序,破壞雜湊值。
- 使用計時安全比較以防止計時攻擊。
- 妥善保管密鑰;定期輪換,並在輪換期間支援多個有效密鑰。
設計重試策略
即使有簽名,傳遞仍可能因網路問題、伺服器停機或逾時而失敗。穩健的重試策略能確保最終傳遞,同時不會壓垮你的系統或發送方。
1. 快速確認,非同步處理
你的 Webhook 端點應盡快回應 2xx 狀態,理想情況下在幾秒內。如果處理需要更長時間,將事件排入佇列進行背景處理。這可防止逾時,並讓發送方認為傳遞成功。
2. 實作帶抖動的指數退避
當傳遞失敗(非 2xx 回應或逾時),發送方應以遞增的延遲進行重試。指數退避(例如 1 秒、2 秒、4 秒、8 秒...)可防止對你的端點造成猛烈衝擊。加入隨機抖動可避免當大量 Webhook 同時失敗時出現的驚群效應。
重試排程範例:
- 第 1 次嘗試:立即
- 第 2 次嘗試:30 秒後
- 第 3 次嘗試:2 分鐘後
- 第 4 次嘗試:10 分鐘後
- 第 5 次嘗試:1 小時後
達到最大重試次數(例如 5 次)後,發送方應停止並通知你(例如透過電子郵件或儀表板)。
3. 使用冪等性處理重複事件
重試可能導致重複傳遞。你的端點必須具備冪等性:多次處理同一事件應與處理一次具有相同效果。使用載荷中的唯一事件 ID 並儲存它。在處理之前,檢查該 ID 是否已見過;如果是,則返回 200 而不重新處理。
常見方法:
- 在事件 ID 上建立資料庫唯一約束。
- 使用 Redis 集合搭配 TTL 儲存最近處理過的 ID。
- 在業務邏輯中使用冪等鍵。
比較:簽名載荷與其他安全措施
| 方法 | 優點 | 缺點 |
|---|---|---|
| HMAC 簽名 | 驗證真實性和完整性;無狀態 | 需要管理共享密鑰 |
| IP 允許清單 | 易於實作 | IP 可能變更;不適合動態環境 |
| 基本驗證 | 易於添加 | 每個請求都發送憑證;安全性較低 |
| 標頭中的 JWT | 可包含聲明和到期時間 | 令牌發行/驗證的額外開銷 |
HMAC 簽名是事實上的標準,因為它們無狀態、高效,且在正確實作時提供強大的安全性。
Webhook 消費者的最佳實踐
- 驗證簽名在每個請求上,即使在開發環境中。
- 快速回應;將繁重處理卸載到佇列。
- 記錄一切——標頭、載荷和處理結果——以供除錯。
- 監控失敗並為重複錯誤設定警報。
- 使用 HTTPS 保護傳輸中的載荷。
- 輪換密鑰定期並在輪換期間支援多個有效密鑰。
Webhook 生產者的最佳實踐
- 簽名載荷使用每個消費者唯一的密鑰。
- 包含唯一事件 ID以協助冪等性。
- 以退避重試並限制嘗試次數。
- 提供儀表板讓消費者查看傳遞日誌和重播事件。
- 清楚記錄你的簽名方案。
測試你的 Webhook 實作
使用 ngrok 等工具公開你的本機伺服器並模擬 Webhook 傳遞。為簽名驗證和冪等性邏輯編寫單元測試。對於負載測試,考慮使用能夠大規模發送簽名請求的工具。
除錯時,檢查原始載荷和標頭。JSON 格式化工具可以幫助視覺化載荷結構。試試我們的 JSON Formatter 快速美化並驗證 Webhook 載荷。
常見問題
什麼是簽名的 Webhook 載荷?
簽名載荷包含加密簽名(通常是 HMAC),讓接收方能夠驗證發送方的身份並確保載荷未被篡改。它是使用共享密鑰和請求主體計算得出的。
Webhook 應該重試多少次?
沒有通用的次數,但常見做法是使用指數退避進行 3–5 次重試。之後,事件被視為失敗,應通知消費者進行調查或手動重播。
我可以使用 JWT 代替 HMAC 進行 Webhook 簽名嗎?
是的,JWT 可行,但它增加了複雜性(令牌發行、到期)。HMAC 更簡單,且對大多數用例來說足夠。根據你現有的基礎設施和要求進行選擇。
準備好檢查和除錯 Webhook 載荷了嗎?使用我們的免費 JSON Formatter 即時格式化和驗證 JSON。