Webhooks 詳解:簽名負載與重試策略

Backend2026-10-30TryQuickToolBox

你已經建立了一個 API,現在需要在事件發生時通知其他服務。輪詢效率低下,因此你實作了 Webhooks。但很快你面臨兩個常見問題:如何確保負載未被篡改,以及當接收端點故障時該怎麼辦。本文說明如何使用簽名負載和重試策略,讓你的 Webhooks 既安全又可靠。

為什麼 Webhooks 需要簽名和重試

Webhooks 是由事件觸發的 HTTP 回呼。它們實作簡單,但引入了安全性和可靠性挑戰。沒有簽名,攻擊者可以偽造請求到你的端點,導致資料損壞或未授權的操作。沒有重試,暫時性故障(如網路波動或臨時伺服器錯誤)會導致事件遺失。

簽名負載解決了真實性問題:發送方使用密鑰對負載簽名,接收方進行驗證。重試解決了可靠性問題:發送方以退避策略重試失敗的交付。

簽名負載如何運作

簽名負載包含一個由請求主體和共享密鑰生成的加密簽名。最常見的方法是使用 SHA-256 的 HMAC(基於雜湊的訊息驗證碼)。

生成簽名

發送方使用密鑰計算原始請求主體的 HMAC。簽名會放在標頭中,通常還會附帶時間戳以防止重放攻擊。

// Node.js 範例:生成 HMAC 簽名
const crypto = require('crypto');

function signPayload(payload, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  return { timestamp, signature };
}

將時間戳和簽名包含在標頭中,例如 X-Webhook-Timestamp 和 X-Webhook-Signature。

驗證簽名

接收方必須使用收到的時間戳和原始主體重建簽名負載,然後計算 HMAC 並安全地進行比較。

// Node.js 範例:驗證簽名
function verifySignature(rawBody, timestamp, receivedSignature, secret) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  // 使用時間安全比較
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}

始終使用時間安全比較以防止計時攻擊。同時,檢查時間戳是否為近期(例如 5 分鐘內)以減輕重放攻擊。

設計重試策略

重試對於可靠的 Webhook 交付至關重要。但簡單的重試可能會壓垮接收方或導致重複處理。良好的重試策略包括:

以下是虛擬碼中的簡單重試迴圈:

function deliverWithRetry(url, payload, maxRetries = 5) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, { method: 'POST', body: payload });
      if (response.ok) return;
    } catch (error) {
      // 記錄錯誤
    }
    attempt++;
    const delay = Math.pow(2, attempt) * 1000; // 指數退避
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  // 移至死信佇列
}

考慮在退避中加入抖動,以避免驚群效應問題。

比較:輪詢 vs 帶重試的 Webhooks

方面 輪詢 帶重試的 Webhooks
延遲 高(取決於間隔) 低(近乎即時)
伺服器負載 恆定(即使沒有事件) 事件驅動
可靠性 高(接收方控制) 取決於重試策略
安全性 標準 API 認證 需要簽名負載

逐步指南:實作帶簽名和重試的 Webhooks

  1. 為每個 Webhook 端點生成密鑰,並安全地共享。
  2. 事件發生時,建構負載(JSON)並使用 HMAC-SHA256 與密鑰和時間戳計算簽名。
  3. 發送 POST 請求 到端點,標頭包含:Content-Type: application/json、X-Webhook-Timestamp 和 X-Webhook-Signature。
  4. 在接收方,使用原始主體和時間戳驗證簽名。如果無效或時間戳太舊則拒絕。
  5. 如果交付失敗(非 2xx 回應或網路錯誤),以指數退避重試,最多嘗試一定次數。
  6. 達到最大重試次數後,記錄失敗並可選地發出警報;將事件儲存在死信佇列中以供手動重放。
  7. 確保接收方的冪等性,使用事件 ID 忽略重複項。

常見陷阱與最佳實踐

常見問題

什麼是簽名的 Webhook 負載?

簽名的 Webhook 負載包含一個加密簽名(例如 HMAC),允許接收方驗證請求來自預期的發送方且未被篡改。

我應該重試失敗的 Webhook 多少次?

常見做法是使用指數退避重試 3–5 次。確切次數取決於你的 SLA 和事件的關鍵性。始終為超過重試限制的事件使用死信佇列。

我可以使用沒有簽名的 Webhooks 嗎?

不建議。沒有簽名,你的端點容易受到偽造請求的攻擊。始終使用簽名(如 HMAC)並驗證時間戳以防止重放攻擊。

在除錯 Webhook 交付時,檢查伺服器日誌至關重要。如果你使用 Nginx,Nginx 日誌分析器 可以幫助你解析和分析存取日誌,以追蹤 Webhook 請求並識別失敗。