Webhook 詳解:簽章驗證與重試機制

Backend2026-10-16TryQuickToolBox

你剛剛整合了一個支付閘道。文件上寫著:「當付款成功時,我們會向你的端點發送一個 POST 請求。」聽起來很簡單,對吧?你建立了一個簡單的路由來接收 JSON、更新資料庫,然後回傳 200。然後事情發生了:客戶的付款成功了,但你的伺服器短暫停機。Webhook 從未送達。或者更糟的是,它送了兩次,而你重複入帳了款項。Webhook 看起來很簡單,直到它出錯為止。

本文將說明 Webhook 的實際運作方式、為什麼簽章載荷對安全性至關重要,以及如何建立重試邏輯來確保你的整合保持可靠——即使出了問題也一樣。

什麼是 Webhook?

Webhook 是一種 HTTP 回呼:與其讓你的應用程式輪詢 API 來偵測變更,提供方會在事件發生時向你指定的 URL 發送 HTTP POST。它是推送而非拉取。

常見的例子包括付款確認(Stripe、PayPal)、CI/CD 建置通知(GitHub、GitLab),以及訊息平台(Slack、Discord)。模式很簡單:你註冊一個 URL,提供方將 JSON 載荷發送到該 URL,你的伺服器負責處理它們。

但這種簡單性隱藏了兩個關鍵挑戰:真實性(你如何知道請求確實來自提供方?)和可靠性(如果你的端點停機或回應緩慢會怎樣?)。

為什麼簽章載荷很重要

你的 Webhook 端點是一個公開的 URL。任何人都可以向它發送 POST 請求。如果沒有驗證,攻擊者可以偽造付款成功事件、觸發出貨,或破壞你的資料。

簽章載荷透過在請求標頭中包含加密簽章來解決這個問題。提供方使用共享密鑰和請求主體計算 HMAC(基於雜湊的訊息驗證碼),然後將其與載荷一起發送。你的伺服器重新計算 HMAC 並進行比對。如果它們相符,則該請求是真實的。

HMAC 簽章如何運作

  1. 提供方與你共享一個密鑰(在設定時提供)。
  2. 對於每個 Webhook,提供方將時間戳和原始請求主體串接起來,然後使用密鑰計算 HMAC-SHA256 雜湊。
  3. 簽章會放在標頭中發送,通常類似 X-Signature 或 Stripe-Signature。
  4. 你的伺服器使用相同的密鑰重新計算 HMAC,並與接收到的簽章進行比對。
  5. 如果它們相符,你就處理該事件;否則,以 401 拒絕。

計算 HMAC 時務必使用原始請求主體。如果你的框架在驗證之前就解析了 JSON,主體可能會被改變(空白字元、鍵的順序),導致驗證失敗。

範例:在 Node.js 中驗證 Webhook 簽章

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; // Buffer

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

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  // 處理事件
  const event = JSON.parse(payload.toString());
  // ... 處理事件

  res.status(200).send('OK');
});

注意使用了 timingSafeEqual 來防止時序攻擊。切勿使用簡單的 === 來比對簽章。

設計重試邏輯

即使有簽章,Webhook 仍然可能失敗。你的伺服器可能停機、網路可能丟失請求,或者你的處理可能超時。提供方通常會重試失敗的傳遞,但你也應該設計系統來優雅地處理重試。

什麼算作失敗?

大多數提供方將任何非 2xx 的回應視為失敗。這包括 3xx 重定向、4xx 客戶端錯誤和 5xx 伺服器錯誤。有些還會強制執行超時(例如 5 秒)。如果你沒有快速回應,該次傳遞就會被標記為失敗並重試。

指數退避與抖動

提供方通常使用指數退避進行重試:第一次重試在 1 分鐘後,然後 5 分鐘、30 分鐘,依此類推。加入抖動(隨機變化)可以防止大量 Webhook 同時失敗時造成的驚群效應。

如果你正在建立 Webhook 發送器,請像這樣實作退避:

在達到最大嘗試次數後,將事件移至死信佇列以供手動檢查。

幂等性:處理重複傳遞

重試意味著你的端點可能會多次收到相同的事件。你的處理必須是幂等的:處理相同事件兩次應該與處理一次具有相同的效果。

常見策略:

Webhook 消費者的最佳實踐

實踐為何重要
驗證簽章防止偽造請求
快速回應(2 秒內)避免超時和不必要的重試
非同步處理將接收與繁重處理解耦
使用幂等處理器安全應對重複傳遞
記錄所有事件有助於除錯和稽核
監控失敗在問題升級前偵測到它們

同時考慮驗證簽章中的時間戳以防止重放攻擊。許多提供方會在簽章載荷中包含時間戳;拒絕超過幾分鐘的舊事件。

Webhook 生產者的最佳實踐

如果你正在建立發送 Webhook 的服務,請遵循以下準則:

常見問題

什麼是簽章 Webhook 載荷?

簽章載荷包含一個加密簽章(通常是 HMAC),該簽章由請求主體和共享密鑰計算得出。接收方驗證簽章以確保請求是真實且未經修改的。

Webhook 應該重試多少次?

沒有通用的次數,但大多數提供方會在幾小時內重試 3 到 5 次。之後,事件通常會被標記為失敗,並可能可供手動重放。無論重試次數為何,你的系統都應該以幂等方式處理重複。

我可以不使用 HTTPS 來使用 Webhook 嗎?

技術上可以,但你應該始終使用 HTTPS。Webhook 通常包含敏感資料,如果沒有 TLS,攻擊者可以攔截或篡改載荷。HTTPS 也能確保你發送到預期的伺服器。

使用 Nginx 日誌除錯 Webhook

當 Webhook 失敗時,第一個要看的地方是你的網頁伺服器日誌。如果你使用 Nginx,存取和錯誤日誌會記錄每個請求,包括狀態碼和回應時間。你可以快速發現模式:重複的 401(簽章驗證失敗)、500(應用程式錯誤)或超時。

若要更快速地分析,請試試 Nginx Log Analyzer 來解析和彙總 Webhook 流量、按狀態碼篩選,並找出有問題的端點。