除錯2026年4月18日閱讀時間 7 分鐘

在正式環境除錯失敗的 Webhook,不賠上你的週末

Webhook 傳遞除錯:整合突然失效時,該在標頭、狀態碼、時間和 payload 裡找什麼。

服務商儀表板記錄了什麼服務商傳遞紀錄2xx送達了,但在回應之後被丟掉4xx是你拒絕的:簽章、WAF、網址錯誤5xx處理程式就在那個時間點拋出錯誤逾時處理程式太慢;把工作改成非同步
從服務商自己的傳遞紀錄開始看。它記下的狀態會告訴你下一步該打開哪一層,省下大約三十分鐘的瞎猜。

一位客戶寄信來:「我的 Stripe Webhook 壞了。」你查 Stripe 儀表板,事件都有發出。你查伺服器的 log,什麼都沒有。Sentry 也沒有。事件確實送到了某個地方,只是不在你預期的地方。

這篇是在正式環境除錯 Webhook 傳遞問題的逐步框架,根據的是最常出現的失敗模式。

重點摘要

  • 先看服務商的儀表板:事件有沒有發出?收到的回應狀態是什麼?
  • 4xx 代表是你拒收了它(簽章、中介軟體、網址錯誤)
  • 5xx 代表處理程式當掉了。到錯誤監控裡查那個確切的時間戳記
  • 逾時代表處理程式太慢。把繁重的工作改成非同步,快速回 200
  • 要解決「花了 2 小時除錯」,就是有一個地方能同時看到收到的請求、轉送出去的請求、重試和重播,通常也就是一個 Webhook 中繼服務(webhook relay)

決策樹

從這裡開始。它能省下 30 分鐘的瞎猜。

Q1:服務商真的有送出事件嗎?

查看服務商的儀表板(Stripe:Developers → Events;GitHub:Settings → Webhooks → Recent Deliveries;Shopify:Dev Dashboard → Apps → 你的應用程式 → Logs)。

如果事件不在那裡,代表觸發條件沒有發生,那就去查本該產生它的上游狀態。如果事件在,就看 Q2。

Q2:服務商有收到 2xx 嗎?

在同一個儀表板裡,看你的端點回傳的回應狀態。

  • 2xx:服務商認為傳遞成功。如果你的應用程式從沒看到它,代表事件到了,卻在回應之後被丟掉。檢查你的非同步佇列、資料庫寫入和處理邏輯。
  • 4xx:你拒收了它。最常見的是簽章驗證失敗、缺少標頭,或中介軟體(WAF、速率限制器、驗證層)擋下了請求。
  • 5xx:你的端點當掉了。查看錯誤監控,而且不是只看「最近一小時」,而是這次傳遞的確切時間戳記。
  • 逾時:你的端點太慢。見 Q4。
  • 連線被拒(connection refused)或 DNS 錯誤:你的端點連不到,這是另一種問題,出在網路、DNS 或防火牆的某處。

Q3:如果是 4xx,是哪一個?

  • 400,請求格式錯誤。通常是標頭不符,或簽章檢查在回傳明確錯誤之前就拋出例外。檢查請求主體格式錯誤或缺少簽章時,處理程式回傳的是什麼。
  • 401 / 403,你把請求當成未授權而拒收。是簽章驗證失敗嗎?還是中介軟體在一個不需要使用者 session 的端點上檢查 session?
  • 404,網址錯了。你是不是部署了一次路由變更,把 Webhook 端點搬走了?
  • 413,payload 太大。調高 body parser 的上限。
  • 429,速率限制器被觸發。不管是什麼在限制 Webhook 路由的速率,都不該這樣做。大多數 Webhook 服務商都從一組有限的 IP 發送,你的限制器可能把它們全當成同一個攻擊者。

Q4:如果是逾時,是什麼慢?

GitHub 給你 10 秒。Shopify 給 5 秒。Slack 和 Discord 給 3 秒。Stripe 從沒公布數字;它的文件只說要在任何耗時邏輯之前回傳 2xx。完整的表格與每一列的出處,都在 Webhook 服務商參考資料。

waitUntil · 這個 200 是你守不住的承諾驗證回傳 200waitUntil(work)只在記憶體裡被回收 → 消失,不會重新傳送先寫入 · 這個 200 是真的驗證持久寫入回傳 200處理可重試
不管實際工作多慢,這個結構都能讓你落在每個服務商的回應時限之內。

大多數逾時,都是因為處理程式裡做了太多事。常見的元凶有:對已經滿載的連線池做同步資料庫寫入;在處理程式裡呼叫其他服務(Stripe、Salesforce、Notion、你自己的 API);同步寄送 email 或 Slack 訊息;以及在一個正在施加背壓(backpressure)的 log 收集器上,把整個請求主體寫到 stdout。

修正的模式是:驗證簽章、把事件排入佇列、回 200。所有商業邏輯都在下游以非同步方式進行。

通常真正壞掉的是什麼

看了幾年下來,這是 80/20 的清單:

  1. 簽章驗證把請求主體重新序列化了。見簽章驗證指南
  2. 代理伺服器或 WAF 在 Webhook 到達處理程式之前就把它吃掉了。檢查 Cloudflare、Vercel,或你的平台用來記錄請求的地方
  3. 速率限制被觸發了。Cloudflare 的 Bot Management 可能會把正當的 Webhook 誤判成惡意流量
  4. 端點網址在某次部署中改了,卻沒有人去服務商那邊更新
  5. 正式環境少了某個環境變數(簽章密鑰、資料庫網址),處理程式就默默地回 500
  6. 背景工作在 Webhook 回 200 之後當掉。服務商以為一切正常,但其實什麼都沒被處理

第 6 項在無伺服器環境裡隨處可見,因為排在回應之後的工作完全沒有持久性保證:「在 Vercel 上接收 Webhook,不讓事件遺失」。如果症狀是重複而不是漏收,那通常是照設計在運作:「同一個 Webhook 你收到了兩次」。

真正有用的工具

服務商的儀表板適合一次看一個事件,不適合看出模式。如果你想看「過去 24 小時內所有失敗的事件」,就只能手動捲動。

ngrok 和 webhook.site 是給本機開發用的。到了正式環境,它們幫不上忙。

你自己的 log 有沒有用,要看你記了什麼。如果你在簽章檢查之前就記下「webhook received for event evt_xxx」,至少能知道它到了。大多數人是在檢查之後才記,於是簽章失敗的事件就完全看不見。

像 AnyHook 這樣的 Webhook 中繼服務,位在處理程式前面,會記錄標頭、請求主體、時間、回應狀態和嘗試次數。除錯流程就變成:看事件串流、按一下失敗的那一個、檢查原始 payload、重播。

AnyHook 的除錯流程

使用者寫信給 AnyHook 客服說「我的 Webhook 不能用」時,儀表板通常不到一分鐘就能讓原因一目瞭然:

  1. 開啟事件串流,篩選 status=failed
  2. 按一下失敗的事件
  3. 看收到的標頭(簽章在不在?)、收到的請求主體(結構符不符合預期?)、轉送後的回應(你的伺服器說了什麼?),以及時間分段(邊緣節點 → 佇列 → 傳遞)
  4. 如果只是偶發狀況,例如一次出包的部署或不穩的網路,就按一下 Replay(重播),看著它成功
  5. 如果每個事件都失敗,你現在也確切知道要修什麼;在你修的同時,事件會一直儲存著,在方案的可重播期間內都能重播(Free 方案 3 天,Pro 方案 30 天,Scale 方案 90 天)

能把 2 小時的除錯縮短成 2 分鐘的,是把服務商實際送出的請求、你的伺服器實際回傳的回應,以及兩者之間的時間戳記,全部放在同一個地方。少了這些,你就得從三個儀表板和自己的 log 裡拼湊出來。

想在註冊之前先看看,一個指令就能拿到可用的接收網址(inbound URL),不需要帳號:

curl -X POST https://anyhook.net/api/v1/quickstart

回應裡有一個 inbound_url,讓你設定給服務商;一個 api_key,用來透過 API 讀取事件紀錄;還有一個 claim_url。這個端點是暫時的:除非你開啟認領網址並登入,否則端點和它的事件會在 7 天後刪除。認領之後,網址維持不變。

結論

如果你的週日下午常常這樣度過,AnyHook 能把大部分的工作變成在儀表板上按一下。

所有文章2026年4月18日 · 7 分鐘

別再漏掉 Webhook。

換一個網址,就有重試、事件紀錄和一鍵重播。或者先親眼看看:註冊後跑一次 30 秒的當機演練,看著一個事件在伺服器當機時保存下來。不需要伺服器。