Webhooks 詳解:簽名載荷與重試機制
你剛剛整合了支付閘道或 CI/CD 服務,現在需要接收即時事件。Webhooks 是標準解決方案,但它們帶來兩個關鍵挑戰:驗證請求確實來自發送方,以及確保伺服器暫時無法使用時事件不會遺失。本文說明如何實作簽名載荷和重試機制,以建構穩健的 webhook 接收器。
什麼是 Webhooks?
Webhook 是一種 HTTP 回呼:每當事件發生時,服務會向您提供的 URL 發送 HTTP POST 請求,而不是讓您的應用程式輪詢 API 以獲取更新。這種推送模型可降低延遲和伺服器負載。然而,由於請求來自外部服務,您必須將其視為不受信任的輸入。
為什麼要簽名載荷?
未經驗證,任何發現您的 webhook 端點的人都可以發送偽造事件,可能觸發未授權的操作。使用共享密鑰對載荷進行簽名,可確認發送方的身分並確保載荷未被篡改。最常見的方法是使用 SHA-256 的 HMAC(基於雜湊的訊息驗證碼)。
HMAC 簽名如何運作
發送方使用密鑰計算原始請求主體的 HMAC 摘要,並將其包含在標頭中(例如 X-Signature)。您的伺服器使用相同的密鑰重新計算 HMAC,並與收到的簽名進行比較。如果匹配,則請求是真實的。
// Node.js 範例:驗證 webhook 簽名
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
// 使用 timingSafeEqual 防止時序攻擊
return crypto.timingSafeEqual(
Buffer.from(digest, 'hex'),
Buffer.from(signature, 'hex')
);
}
重要: 始終使用常數時間比較函數(如 timingSafeEqual),以避免透過時序差異洩露資訊。此外,請對原始主體計算 HMAC,而不是對解析後的 JSON,因為重新序列化可能會改變位元組序列。
實作重試
即使有簽名,網路問題或伺服器錯誤也可能導致 webhook 交付失敗。可靠的發送方會重試失敗的交付,通常採用指數退避。作為接收方,您應將端點設計為冪等,並返回適當的狀態碼。
從發送方角度的重試策略
- 在 5xx 和網路錯誤時重試: 這些表示暫時性問題。
- 不要在 4xx 時重試: 用戶端錯誤(例如 400 Bad Request)通常是永久性的;重試無濟於事。
- 使用指數退避: 每次重試之間等待更長時間,以避免壓垮接收方。
- 設定最大重試次數: 達到限制後,將交付標記為失敗並發出警報。
設計冪等接收器
因為重試可能導致重複交付,您的接收器必須能夠多次處理同一事件而不產生不良影響。在載荷(或標頭)中包含唯一的事件 ID,並儲存已處理的 ID。如果事件 ID 已被處理,立即返回 200 OK,無需重新處理。
// 冪等 webhook 處理的虛擬碼
function handleWebhook(event) {
if (processedEvents.has(event.id)) {
return { status: 200, body: 'Already processed' };
}
// 處理事件...
processedEvents.add(event.id);
return { status: 200, body: 'Success' };
}
Webhook 安全性與可靠性的最佳實踐
- 使用 HTTPS: 始終透過 TLS 提供您的 webhook 端點,以防止竊聽。
- 驗證時間戳記: 如果簽名包含時間戳記,拒絕超過幾分鐘的請求,以防止重放攻擊。
- 快速回應: 在進行繁重處理之前,先以 2xx 狀態確認收到。將工作非同步排入佇列。
- 記錄一切: 保留收到的 webhook 日誌,包括標頭和載荷(遮蔽敏感資料),以便除錯。
- 定期輪換密鑰: 支援多個作用中的密鑰,以實現無縫輪換。
比較:輪詢 vs. Webhooks
| 方面 | 輪詢 | Webhooks |
|---|---|---|
| 延遲 | 取決於間隔 | 近乎即時 |
| 伺服器負載 | 高(頻繁請求) | 低(僅在事件發生時) |
| 複雜度 | 實作簡單 | 需要簽名驗證和重試處理 |
| 可靠性 | 您控制重試 | 取決於發送方的重試策略 |
常見問題
如何驗證 webhook 簽名?
使用共享密鑰計算原始請求主體的 HMAC,然後使用常數時間比較函數將其與標頭中的簽名進行比較。
我的 webhook 端點應該返回什麼?
一旦收到事件,立即返回 2xx 狀態碼(例如 200 OK)。如果您返回 4xx 或 5xx,發送方可能會重試或將交付標記為失敗。
我可以不使用簽名來使用 webhooks 嗎?
不建議這樣做。沒有簽名,您無法驗證發送方,使您的端點容易受到偽造請求的攻擊。在生產環境中始終使用簽名。
在除錯 webhook 載荷時,JSON 格式化至關重要。使用我們的 JSON 格式化工具 快速美化並驗證傳入的 JSON 資料。