什麼是 Webhook?白話文完整教學:與 API 輪詢的差異、Python FastAPI 實作與安全驗證

Webhook 是讓伺服器在事件發生時主動用 HTTP POST 推送資料到指定網址的通知機制,又稱「反向 API」。本文用白話解釋 Webhook 原理、與 API 輪詢的差異比較表、金流支付與 CI/CD 等實際應用、Python FastAPI 接收實作(含 HMAC 簽名驗證)、以及重試與防重播等安全最佳實踐,適合初學者一次搞懂。

  • Dennis
  • 5 分鐘閱讀
什麼是 Webhook?白話文完整教學:與 API 輪詢的差異、Python FastAPI 實作與安全驗證

Webhook 是讓伺服器在事件發生時主動用 HTTP POST 推送資料到指定網址的通知機制,又稱「反向 API」:你不用一直去問「好了沒」,系統會在事件發生當下自己通知你,常用於付款成功通知、程式碼推送觸發 CI/CD、CRM 與自動化工具串接等即時場景。

如果你寫過程式,一定用過 API:你的程式發出請求、對方回傳資料,這是「你去問」。Webhook 反過來——對方有事情發生時,主動把資料丟到你的網址。差別就像你每隔一小時傳訊息問朋友「你到了嗎?」(輪詢),vs 朋友到了才傳訊息給你(Webhook)。

sequenceDiagram
    participant S as 事件來源<br>(Stripe / GitHub / n8n)
    participant R as 你的伺服器<br>(Webhook 端點)
    S->>S: 事件發生<br>(付款成功 / push / 表單送出)
    S->>R: HTTP POST (JSON payload + 簽名)
    R->>R: 驗證簽名 + 時間戳
    R-->>S: 立即回 2xx (200 OK)
    R->>R: 非同步處理業務邏輯
    Note over S,R: 若未收到 2xx → 依重試策略重送

Webhook 與 API 輪詢的差異

比較項目API 輪詢(Polling)Webhook
誰主動你的程式定時去問(如每小時)事件源主動推送
即時性低,取決於輪詢頻率極高,事件當下即送達
資源消耗高且浪費(大多請求沒新資料)極低,只有事件發生才送
適合場景資料更新非常頻繁、不需要極致即時、對方沒提供 Webhook需要即時反應、更新頻率不固定、事件驅動架構
可靠性API 通常內建重試需要自己處理失敗重試
安全性OAuth / API Key簽名驗證是關鍵

什麼時候該選輪詢?舉例來說,你串接企業客服系統要同步「所有」ticket 更新,ticket 數量可能上千筆,Webhook 反而會把你的伺服器灌爆——這種大量、高頻、不需要毫秒級即時的場景,輪詢比較穩。但如果你要「使用者一付款就立刻開通權限」,Webhook 是唯一合理的選擇。

Webhook 實際應用案例

  • 金流支付:Stripe、PayPal 在付款成功、退款、爭議(chargeback)發生時送 Webhook,你的系統即使在使用者關閉瀏覽器後,依然能拿到最終付款狀態。
  • CI/CD 自動化:開發者 push 程式碼到 GitHub 時,GitHub 送 Webhook 觸發 Jenkins/GitHub Actions 自動建置、測試、部署,不用 Jenkins 每分鐘去問一次。
  • CRM 與自動化工具:HubSpot 表單送出後送 Webhook,自動觸發 Slack 通知業務、寄送客製化 Email、更新潛在客戶分數。
  • 無程式碼自動化:n8n、Zapier、Make 等工具都可以用 Webhook 當「觸發器」,讓外部事件直接啟動自動化流程——這正是 no-code 串接的核心機制。

用 Python FastAPI 接收 Webhook(實作)

接收 Webhook 本質上就是開一個 HTTP POST 端點。最關鍵的陷阱:必須用原始請求主體(raw body)做簽名驗證,很多框架會先把 JSON 解析再轉字串,任何一個位元組的變動都會讓簽名對不上。

from fastapi import FastAPI, Request, Response, status
from svix.webhooks import Webhook, WebhookVerificationError

app = FastAPI()

# 從 Webhook 供應商後台取得的簽名密鑰(例如 whsec_ 開頭)
SECRET = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

@app.post("/webhook/")
async def webhook_handler(request: Request):
    headers = request.headers
    # ⚠️ 重點:用 await request.body() 取得原始 bytes,不要用 request.json()
    payload = await request.body()

    try:
        wh = Webhook(SECRET)
        # 驗證簽名與時間戳,防偽造與重播攻擊
        msg = wh.verify(payload, headers)
    except WebhookVerificationError:
        return Response(status_code=status.HTTP_400_BAD_REQUEST)

    # 這裡再做實際業務處理(建議丟進 queue 非同步處理)
    print("收到事件:", msg)
    return Response(status_code=status.HTTP_204_NO_CONTENT)

沒有 Svix 函式庫時,自己用 HMAC 驗證也只要幾行:

import hmac, hashlib

def verify_signature(secret: str, payload: bytes, signature: str) -> bool:
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    # 用 hmac.compare_digest 做恆定時間比較,避免時序攻擊
    return hmac.compare_digest(expected, signature)

開發期想測試本地端點,可以用 Svix Play、Pinggy、ngrok 之類的工具把 localhost 暴露成公開網址,直接接收真實的 Webhook 請求。

Webhook 安全性最佳實踐

沒做防護的 Webhook 端點等於對全世界開放 POST,攻擊者可以偽造事件(例如偽造「已付款」通知)。以下是必做的四件事:

  1. HMAC 簽名驗證:供應商用共享金鑰對 payload 做 SHA-256 簽名,接收端必須比對簽名,並用 hmac.compare_digest 恆定時間比較防時序攻擊。絕不信任未驗證的資料。
  2. 強制 HTTPS:絕不用明文 HTTP 收 Webhook,避免中間人竄改 payload 與金鑰。
  3. 時間戳防重播:Webhook 標頭應帶時間戳,拒絕超過 5 分鐘(一般業界標準)的請求,防止攻擊者攔截有效 payload 重送。
  4. 冪等性設計:供應商可能重送同一事件(例如你的端點回應太慢觸發重試),handler 必須能優雅處理重複——用 payload 裡的事件 ID 記錄「已處理過」,避免重複扣款、重複發信。

失敗重試機制:指數退避與死信佇列

接收端離線、超時、回 5xx 都會觸發供應商重送。常見的重試策略:

  • 固定間隔:每 5 分鐘重送一次。實作簡單,但系統級故障時容易引發「重試風暴」。
  • 指數退避:等待時間指數成長(30 秒、1 分、2 分、4 分……,通常有上限)。對系統友善,但高優先級事件會等比較久。
  • 混合策略(業界推薦):失敗後前幾分鐘快速重試 1–2 次,之後轉為有上限的指數退避。

重試多次仍失敗(例如 5 次)的事件會進入死信佇列(DLQ),等工程師人工介入處理,而不是無止盡重試。實務上常用 Redis 的 List 當 FIFO 佇列、Sorted Set 做延遲排程,就能自己打造一套可靠的重試系統。

常見誤解:Webhook 不是 WebSocket

兩者都是「事件驅動」,但完全不同:Webhook 是伺服器對伺服器、走標準 HTTP POST、單向通知、每次送達都是獨立交易;WebSocket 是雙向即時連線,適合聊天室、即時行情這類需要持續雙向溝通的場景。Webhook 不需要長連線,也因此更簡單、更容易水平擴展。

總結

Webhook 是現代系統串接的基礎設施:事件發生 → HTTP POST → 驗證 → 處理 → 回 2xx。掌握「何時用 Webhook、何時用輪詢」的判斷,加上簽名驗證、時間戳、冪等性這三招,就足以應付絕大多數金流、CI/CD 與自動化整合需求。

延伸閱讀

資料來源

📬 訂閱 most.tw 電子報

每週精選 AI 工具教學與技術乾貨,直接送到你的信箱。免費、隨時可退訂。