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,攻擊者可以偽造事件(例如偽造「已付款」通知)。以下是必做的四件事:
- HMAC 簽名驗證:供應商用共享金鑰對 payload 做 SHA-256 簽名,接收端必須比對簽名,並用
hmac.compare_digest恆定時間比較防時序攻擊。絕不信任未驗證的資料。 - 強制 HTTPS:絕不用明文 HTTP 收 Webhook,避免中間人竄改 payload 與金鑰。
- 時間戳防重播:Webhook 標頭應帶時間戳,拒絕超過 5 分鐘(一般業界標準)的請求,防止攻擊者攔截有效 payload 重送。
- 冪等性設計:供應商可能重送同一事件(例如你的端點回應太慢觸發重試),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 與自動化整合需求。
延伸閱讀
- 自動化工作流工具大比拚:Make、n8n、Zapier、Dify 哪款最適合你?
- 探索2023年八大API架構風格:從SOAP到AMQP的介紹
- 企業API整合MCP難度高不高?AI時代的API升級攻略!
- Tokens vs API Keys:你真的懂這兩個網路安全大將的差異嗎?
