Webhooks 詳解:簽名負載與重試邏輯
你已經建立了一個 API,現在需要在事件發生時通知其他服務。輪詢效率低下,所以你轉向 Webhooks。但很快你會發現 Webhooks 可能失敗、被偽造或導致重複處理。本文說明如何使用簽名負載保護 Webhooks,並實作穩健的重試邏輯。
什麼是 Webhooks?
Webhooks 是使用者定義的 HTTP 回呼。當你的系統中發生事件(例如付款成功),你會向訂閱者提供的 URL 發送 HTTP POST 請求。負載通常包含 JSON 或表單格式的事件資料。
Webhooks 讓服務之間能夠即時通訊,無需不斷輪詢。它們被支付閘道、CI/CD 工具和 SaaS 平台廣泛使用。
為什麼要保護 Webhooks?
沒有安全防護,任何知道 Webhook URL 的人都可以發送偽造請求,導致資料損壞或詐騙。此外,攻擊者可能重放舊請求。使用共享密鑰對負載簽名可確保真實性和完整性。
使用 HMAC 簽署負載
HMAC(基於雜湊的訊息驗證碼)是簽署 Webhooks 的常見方法。發送方和接收方共享一個密鑰。發送方使用密鑰計算負載的 HMAC,並將其包含在標頭中(例如 X-Signature)。接收方重新計算 HMAC 並進行比較。
逐步說明:發送簽名 Webhook
- 建立負載(JSON)。
- 使用密鑰計算負載的 HMAC-SHA256。
- 將十六進位摘要包含在標頭中,例如
X-Signature: sha256=...。 - 發送 POST 請求。
// Node.js 範例,使用 crypto
const crypto = require('crypto');
const secret = 'your_shared_secret';
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// 發送 POST 請求,標頭包含 X-Signature: sha256=signature
驗證簽名
在接收方,提取簽名標頭,計算原始請求主體的 HMAC,並使用恒定時間比較來防止時序攻擊。
// Express.js 範例
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const computed = crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Invalid signature');
}
// 處理負載
res.status(200).send('OK');
});
注意:確保使用原始主體,而不是解析後的 JSON,因為解析可能會改變負載。
防止重放攻擊
在簽名負載中包含時間戳,並拒絕超過幾分鐘的請求。同時,包含唯一的事件 ID 並儲存已處理的 ID 以避免重複。
可靠交付的重試邏輯
Webhook 端點可能離線或緩慢。實作具有指數退避和抖動的重試。設定最大重試次數(例如 5 次)和每個請求的超時時間。
重試策略
- 指數退避:等待 1 秒、2 秒、4 秒、8 秒等。
- 抖動:加入隨機性以避免驚群效應。
- 冪等性:使用冪等鍵,使重複交付不會導致重複操作。
範例重試排程:第 1 次重試在 5 秒後,第 2 次在 30 秒後,第 3 次在 2 分鐘後,第 4 次在 10 分鐘後,第 5 次在 1 小時後。
處理失敗
如果所有重試都失敗,記錄失敗並通知訂閱者(例如透過電子郵件或儀表板)。提供手動重新發送 Webhooks 的方式。
比較:簽名負載 vs. 基本驗證
| 方法 | 安全性 | 複雜度 | 重放保護 |
|---|---|---|---|
| HMAC 簽名負載 | 高 | 中等 | 是(含時間戳) |
| 基本驗證 | 低 | 低 | 否 |
| 標頭中的 API 金鑰 | 中 | 低 | 否 |
最佳實踐
- 始終對 Webhook 端點使用 HTTPS。
- 定期輪換密鑰。
- 在處理前驗證負載結構。
- 快速回應(在 2xx 內)並非同步處理。
- 監控 Webhook 交付指標(成功率、延遲)。
常見問題
如何驗證 Webhook 簽名?
使用你的密鑰計算原始請求主體的 HMAC,並使用恒定時間比較將其與簽名標頭進行比較。
如果 Webhook 失敗該怎麼辦?
實作具有指數退避和抖動的重試。如果所有重試都失敗,記錄失敗並提供手動重新發送選項。
我可以使用未簽名的 Webhooks 嗎?
不建議。沒有簽名,你無法驗證發送方,使你的系統容易受到偽造和重放攻擊。
測試 Webhook 負載時,你可能需要檢查 JSON 回應。使用我們的 JSON Formatter 快速美化並驗證 JSON 資料。