Webhooks 詳解:簽章負載與重試機制
你剛剛使用 webhooks 整合了一個支付閘道。在測試環境中一切正常,但在生產環境中,事件有時會遺失,或者你會收到重複的通知,導致重複扣款。根本原因通常在於你如何處理簽章負載與重試。本文將解釋 webhooks 的運作機制,並提供實用步驟來建立穩健且安全的整合。
什麼是 Webhooks?
Webhooks 是使用者定義的 HTTP 回呼。當來源系統中發生事件時(例如,一筆付款被處理),它會向你指定的 URL 發送一個包含事件資料的 HTTP POST 請求。與輪詢不同,webhooks 以近乎即時的方式推送資料,可降低延遲與伺服器負載。
然而,webhooks 也帶來挑戰:驗證真實性、處理失敗,以及確保僅處理一次。讓我們逐一解決。
簽章負載:驗證真實性
由於 webhook 端點是公開可存取的,任何人都可以發送偽造請求。為了防止這種情況,提供者會使用密鑰對負載進行簽章。你驗證簽章以確保請求是真實的。
HMAC 簽章的運作方式
大多數提供者使用 HMAC(雜湊訊息鑑別碼)搭配 SHA-256。提供者使用請求主體與共享密鑰計算簽章,然後將其包含在標頭中(例如 X-Signature)。你在自己這邊重新計算簽章並進行比對。
Python 範例:
import hmac
import hashlib
def verify_signature(payload, secret, received_signature):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_signature)
重要: 使用原始請求主體,而非解析後的 JSON,因為解析可能會改變空白字元並破壞簽章。
常見陷阱
- 使用解析後的資料: 務必針對原始位元組進行驗證。
- 計時攻擊: 使用恆定時間比較(例如
hmac.compare_digest)。 - 忽略時間戳: 部分提供者會在簽章中包含時間戳以防止重放攻擊。請驗證其落在容忍範圍內(例如 5 分鐘)。
重試:優雅地處理失敗
你的端點可能暫時無法使用,或者網路問題可能導致失敗。可靠的 webhook 系統會以指數退避重試失敗的傳遞。
重試策略
提供者通常會在非 2xx 回應或逾時時重試。常見模式:
| 策略 | 說明 | 範例 |
|---|---|---|
| 固定間隔 | 每 N 秒重試一次 | 每 30 秒,最多 5 次 |
| 指數退避 | 每次嘗試將延遲加倍 | 1 秒、2 秒、4 秒、8 秒... |
| 指數退避加抖動 | 加入隨機性以避免驚群效應 | 1 秒 ± 0.5 秒、2 秒 ± 1 秒... |
作為接收方,你無法控制發送方的重試策略,但你可以設計端點使其具備韌性。
接收方的最佳實踐
- 快速回應: 在幾秒內回傳 2xx。將處理卸載至背景工作。
- 保持冪等: 使用負載中的冪等鍵以避免重複處理。
- 記錄所有內容: 儲存傳入的 webhooks 以供除錯與重放。
- 監控失敗: 為重複失敗設定警報。
實作冪等性
重複會發生:發送方可能因為你的回應緩慢而重試,或者你可能意外處理同一個事件兩次。冪等性確保多次處理一個事件與處理一次具有相同的效果。
使用唯一的事件 ID(通常在負載或標頭中提供),並將其儲存在具有唯一約束的資料庫中。在處理之前,檢查該 ID 是否存在;如果存在,則跳過。
def process_event(event_id, data):
if EventLog.exists(event_id):
return # already processed
EventLog.create(event_id)
# process data...
對於高吞吐量的系統,使用分散式鎖或資料庫交易以避免競爭條件。
安全性考量
除了簽章驗證之外,還需考慮:
- HTTPS: 務必使用 TLS 以防止竊聽。
- IP 允許清單: 如果提供者發布 IP 範圍,限制傳入請求。
- 速率限制: 保護你的端點免受濫用。
- 負載驗證: 即使在簽章驗證之後,仍要驗證資料結構描述以避免注入攻擊。
在本機測試 Webhooks
在開發期間,你需要一個公開 URL 來接收 webhooks。像 ngrok 或 localtunnel 這類工具可以將你的本機伺服器暴露出去。或者,許多提供者提供 CLI 將事件轉發到你的 localhost。
模擬失敗以測試重試處理:故意回傳 500 錯誤並觀察提供者的重試模式。
常見問題
如何驗證 webhook 簽章?
使用 HMAC 搭配共享密鑰。計算原始請求主體的雜湊,並使用恆定時間比較函式將其與簽章標頭進行比對。
如果收到重複的 webhooks 該怎麼辦?
透過儲存事件 ID 來實作冪等性。在執行業務邏輯之前,檢查該事件是否已經處理過。
我可以僅依賴 webhook 重試來確保可靠性嗎?
不行。重試有幫助,但無法保證傳遞。對於關鍵事件,將 webhooks 與輪詢備援或持久化事件的訊息佇列結合使用。
在除錯 webhook 負載時,你可能需要檢查 JSON 資料。使用我們的 JSON Formatter 來快速美化列印並驗證負載。