Webhooks 詳解:簽章負載與重試機制

Backend2026-10-09TryQuickToolBox

你剛整合完一個支付閘道,現在需要在付款成功時更新資料庫。每隔幾秒輪詢 API 既浪費資源又緩慢。Webhooks 透過在事件發生時將其推送至你的伺服器來解決這個問題。但若缺乏適當的安全性與可靠性,webhooks 可能成為錯誤與漏洞的來源。本文說明如何正確實作 webhooks,重點放在簽章負載與重試機制。

什麼是 Webhooks?

Webhook 是一種 HTTP 回呼:當服務中發生事件時,該服務會向你提供的 URL 發送 HTTP POST 請求。負載通常包含關於該事件的 JSON 資料。例如,當客戶完成付款時,Stripe 會向你的端點發送 charge.succeeded 事件。

Webhooks 採用推送式機制,因此與輪詢相比可降低延遲與伺服器負載。然而,它們也帶來挑戰:你如何確認請求是真實的?如果事件觸發時你的伺服器正好停機怎麼辦?

為什麼簽章負載很重要

任何人都可以向你的 webhook 端點發送 HTTP POST。若未經驗證,攻擊者可以偽造事件,導致未經授權的操作,例如將訂單標記為已付款。簽章負載透過包含只有發送方與你能產生的加密簽章來解決此問題。

最常見的方法是使用共享密鑰的 HMAC(雜湊式訊息驗證碼)。發送方使用密鑰計算負載的雜湊值,並將其包含在標頭中。你重新計算雜湊值並進行比對。若兩者相符,則該請求是可信的。

如何驗證簽章負載

以下是使用 Node.js 與 Express 的逐步做法:

  1. 以字串形式取得原始請求主體。驗證前請勿將其解析為 JSON,因為解析可能會改變位元組序列。
  2. 從標頭中提取簽章(例如 X-Signature)。
  3. 使用你的密鑰與原始主體計算 HMAC。
  4. 使用恆定時間比較法將計算出的簽章與收到的簽章進行比對,以防止時序攻擊。
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-signature'];
  if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // Process event
  res.status(200).send('OK');
});

請務必使用強密鑰並定期輪換。切勿在客戶端程式碼中暴露密鑰。

實作帶退避的重試機制

Webhooks 透過網際網路傳遞,因此失敗在所難免。你的伺服器可能停機,或網路可能出現暫時性問題。穩健的 webhook 系統會以指數退避方式重試失敗的傳遞。

通常,若發送方在逾時時間內(例如 5 秒)未收到 2xx 回應,便會進行重試。重試排程可能為:立即重試,然後在 1 分鐘、5 分鐘、30 分鐘、2 小時後等,直到達到最大嘗試次數(例如 5 次)。

作為接收方,你必須快速回應以避免逾時。以 200 OK 確認收到,並以非同步方式處理事件。

處理幂等性

由於重試可能導致重複傳遞,你的事件處理必須具備幂等性。在負載中包含唯一的事件 ID,並儲存已處理的 ID。處理前,檢查該 ID 是否已被處理過。

async function processEvent(event) {
  const { id, type, data } = event;
  if (await db.processedEvents.findOne({ id })) {
    return; // Already processed
  }
  await db.processedEvents.insertOne({ id });
  // Handle event based on type
}

使用資料庫交易來確保事件僅在成功處理後才被標記為已處理。

Webhook 安全性的最佳實踐

比較:輪詢 vs Webhooks

面向輪詢Webhooks
延遲高(基於間隔)低(近乎即時)
伺服器負載高(持續請求)低(僅在事件發生時)
複雜度實作簡單需要安全性與重試機制
可靠性取決於輪詢頻率取決於重試邏輯

常見問題

如何在本機測試 webhooks?

使用 ngrok 等工具將你的本機伺服器暴露至網際網路。設定 webhook 提供者將事件發送至你的 ngrok URL。

如果 webhook 發送時我的伺服器正好停機怎麼辦?

發送方應以退避方式重試。確保你的伺服器具備高可用性並快速回應,以避免逾時。

我可以使用非對稱簽章代替 HMAC 嗎?

可以,部分提供者使用 RSA 或 ECDSA 簽章。你使用他們的公鑰進行驗證。HMAC 較簡單,但需要共享密鑰。

在除錯 webhook 負載時,將 JSON 格式化以便閱讀會很有幫助。試試我們的 JSON Formatter,立即美化列印並驗證 webhook 負載。