Webhooks 詳解:簽名載荷與重試機制
你剛剛整合了支付提供商的 webhook 來接收交易更新。在測試環境中一切正常,但在生產環境中,你注意到在流量高峰時會遺失事件,或者當提供商重試時會出現重複扣款。Webhooks 概念簡單但實作起來卻很棘手:它們跨越網路邊界,可能延遲、重複或被偽造。本文說明如何使用簽名載荷和智慧重試策略來建構穩健的 webhook 消費者和生產者。
什麼是 Webhooks?
Webhooks 是由事件觸發的使用者定義 HTTP 回呼。當來源系統發生事件(例如新訂單)時,它會向您提供的 URL 發送 HTTP POST 請求。與輪詢不同,webhooks 以近乎即時的方式推送資料,降低延遲和伺服器負載。
然而,webhooks 帶來了挑戰:確保請求確實來自預期的發送方、優雅地處理失敗,以及避免重複處理。
使用簽名載荷保護 Webhooks
若未經驗證,任何人都可以向您的 webhook 端點發送偽造請求。簽名載荷透過包含可驗證的加密簽名來解決此問題。
HMAC 簽名如何運作
發送方使用共享密鑰和請求主體計算基於雜湊的訊息驗證碼(HMAC)。簽名會放在標頭中發送(例如 X-Signature)。您在己方重新計算 HMAC 並進行比較。
// Node.js 範例:驗證 webhook 簽名
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
請務必使用常數時間比較函數,例如 timingSafeEqual,以防止時序攻擊。
包含時間戳
為了防止重放攻擊,請在簽名載荷中包含時間戳,並拒絕超過幾分鐘(例如 5 分鐘)的請求。時間戳應是簽名資料的一部分。
實作可靠的重試
網路問題或暫時性伺服器錯誤可能導致 webhook 傳遞失敗。穩健的發送方會使用指數退避重試失敗的傳遞。
重試策略
- 指數退避: 每次重試之間等待更長時間(例如 1 秒、2 秒、4 秒、8 秒...)。
- 最大重試次數: 設定限制(例如 5 次嘗試)以避免無限迴圈。
- 死信佇列: 達到最大重試次數後,儲存事件以供手動檢查。
- 冪等性: 包含唯一事件 ID,以便接收方可以去重。
範例:Python 中的退避重試
import time
import requests
from requests.exceptions import RequestException
def send_webhook(url, payload, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload, timeout=5)
if response.status_code < 500:
return True # 成功或客戶端錯誤(不重試)
except RequestException:
pass
time.sleep(2 ** attempt) # 指數退避
return False
Webhook 消費者的最佳實踐
- 快速回應: 在處理之前,先以 2xx 狀態碼確認收到。將工作非同步排入佇列。
- 驗證簽名: 在信任載荷之前,務必驗證簽名。
- 處理重複: 使用事件 ID 確保冪等處理。
- 記錄一切: 保留接收到的 webhook 日誌以供除錯。
比較:Webhooks 與輪詢
| 面向 | Webhooks | 輪詢 |
|---|---|---|
| 延遲 | 近乎即時 | 取決於間隔 |
| 伺服器負載 | 低(僅推送) | 高(頻繁請求) |
| 複雜度 | 較高(需要端點、安全性) | 較低 |
| 可靠性 | 需要重試 | 本質上會重試 |
常見問題
什麼是簽名 webhook 載荷?
簽名載荷包含加密簽名(通常是 HMAC),讓接收方能夠驗證請求的真實性和完整性。
失敗的 webhook 應該重試多少次?
常見做法是使用指數退避重試 3–5 次,但這取決於您的 SLA 和事件的關鍵程度。
我可以不使用 HTTPS 使用 webhooks 嗎?
不行,請務必使用 HTTPS 來保護載荷和簽名免於攔截。純 HTTP 會暴露敏感資料。
在除錯 webhook 載荷時,您可能需要快速格式化 JSON。試試我們的 JSON Formatter 來美化列印和驗證 JSON 回應。