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

Backend2026-10-17TryQuickToolBox

你剛整合了一個支付閘道。文件上寫著:「當付款成功時,我們會發送 webhook 到你的端點。」你建立了一個簡單的路由,在本機測試過後就部署上線。然後在正式環境中,你開始注意到重複的事件、遺漏的通知,以及神秘的 500 錯誤。Webhook 看起來很簡單——直到它不再簡單。

本指南將說明 webhook 的實際運作方式、為什麼簽章負載很重要,以及如何建立重試邏輯,讓你的系統即使出錯也能保持同步。

什麼是 Webhook?

Webhook 是一種 HTTP 回呼:當特定事件發生時,某個系統會向你提供的 URL 發送 HTTP POST 請求。與輪詢(你不斷詢問「有發生什麼事嗎?」)不同,webhook 會以接近即時的方式將事件推送給你。

常見用途包括:

發送方(例如 Stripe、GitHub)是 webhook 提供者;你的伺服器則是 webhook 消費者。

為什麼簽章負載不可或缺

你的 webhook 端點是公開可存取的。任何知道 URL 的人都可以發送假事件。如果沒有驗證,攻擊者就能偽造「付款成功」事件,而你可能會免費出貨。

簽章負載透過在請求標頭中加入密碼學簽章來解決這個問題,該簽章使用只有你和提供者知道的共享密鑰產生。當你收到 webhook 時,重新計算簽章並進行比對。如果相符,表示負載是真實的。

HMAC 簽章的運作方式

大多數提供者使用 HMAC-SHA256。提供者會計算:

signature = HMAC-SHA256(secret, payload)

並將其放在如 X-Signature 的標頭中發送。你的伺服器執行相同的計算並檢查是否相等。由於密鑰是保密的,只有提供者能產生有效的簽章。

實際驗證簽章

以下是使用 Express 的簡易 Node.js 範例:

const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const payload = req.body; // raw Buffer

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(payload);
  console.log('Received event:', event.type);
  res.status(200).send('OK');
});

app.listen(3000);

重要:務必使用原始請求主體進行簽章驗證。如果你先解析 JSON,序列化後的字串可能會不同(空白、鍵的順序),導致簽章檢查失敗。

常見陷阱

重試:優雅地處理失敗

網路會故障、伺服器會重啟,部署也會發生。Webhook 提供者預期你的端點可能暫時無法使用,因此會重試失敗的傳遞。但重試會帶來新的挑戰:重複事件、亂序傳遞,以及如果不小心的話可能出現的無限迴圈。

提供者如何重試

大多數提供者使用指數退避:第一次失敗後等待短時間,然後間隔逐漸拉長。例如,Stripe 會以遞增的延遲重試最多 3 天。GitHub 最多重試 8 次。確切的排程各不相同,但模式是一致的。

你的端點應快速回傳 2xx 狀態碼以確認收到。任何其他狀態(4xx、5xx)都表示失敗並觸發重試。

建立冪等處理器

由於重試可能多次傳遞相同事件,你的處理器必須是 冪等 的:處理相同事件兩次應與處理一次有相同的效果。

常見策略:

使用簡單記憶體集合的範例(僅供說明;正式環境請使用持久化儲存):

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const event = req.body;
  if (processedEvents.has(event.id)) {
    return res.status(200).send('Already processed');
  }
  // Process event...
  processedEvents.add(event.id);
  res.status(200).send('OK');
});

快速回應

提供者通常有逾時限制(例如 5 秒)。如果你的處理器需要大量處理,請立即確認並以非同步方式處理。例如,將事件推送到佇列並回傳 200。

可靠 Webhook 的最佳實務

  1. 務必驗證簽章,使用原始主體和固定時間比較。
  2. 使用 HTTPS 保護你的端點,防止竊聽和竄改。
  3. 實作冪等性以處理重複傳遞。
  4. 記錄所有內容——傳入的負載、簽章和你的回應——以便除錯。
  5. 監控失敗並為重複錯誤設定警示。
  6. 使用真實事件測試,透過提供者儀表板或 CLI 工具。
  7. 處理亂序事件,在邏輯中加入時間戳或序號。

比較:輪詢 vs. Webhook

面向輪詢Webhook
延遲取決於間隔接近即時
伺服器負載高(持續請求)低(僅事件發生時)
複雜度易於實作需要端點、安全性、重試
可靠性你控制重試提供者控制重試

常見問題

什麼是 webhook 簽章?

Webhook 簽章是負載的密碼學雜湊(通常是 HMAC-SHA256),使用共享密鑰產生。它讓接收方驗證請求來自預期的發送方,且未被竄改。

如何處理重複的 webhook 事件?

透過追蹤事件 ID 或使用天生冪等的資料庫操作,讓你的處理器具備冪等性。在套用變更前,檢查事件是否已處理過。

我的 webhook 端點應該回傳什麼?

在你收到並驗證事件後,盡快回傳 2xx 狀態碼(例如 200 OK)。如果需要大量處理,請立即確認並以非同步方式處理工作。

準備好測試你的 webhook 負載了嗎?使用我們的 JSON Formatter 來檢查並驗證傳入事件的 JSON 結構。