跳轉到主要內容

收不到支付 Webhook 回調怎麼辦?排查原因並補回漏掉的事件

收不到支付 Webhook 回調時的排查清單:先看投遞記錄,再查回調地址、超時、驗簽和事件訂閲,最後通過事件接口補回漏掉的事件。

發佈於 約 6 分鐘閲讀

手繪插畫:一個信封被帶紅叉的服務器彈回,下方是 30s、2m、10m、30m、2h 的重試時間線和一個放大鏡,旁邊一個雙手插兜、一臉擔心的人
本文目錄
  1. 第一步:回調到底發出去了嗎?
  2. 有沒有訂閲這個事件?
  3. 回調地址能從公網訪問嗎?
  4. 你的處理代碼拒絕了它嗎?
  5. 是不是超時了?
  6. Webhook 是怎樣重試的?
  7. 如何補回漏掉的 Webhook 事件
  8. 端到端測試 Webhook
  9. 不要讓一次漏收的回調決定訂單結果
  10. 總結

收不到支付 Webhook 回調是最常見的對接問題之一,而原因幾乎總是那麼幾種:沒有訂閲事件的端點,請求根本沒發出;發到了一個返回錯誤或重定向的地址;處理時間超過了超時限制;或者請求到了,卻被你自己的代碼拒絕了。好消息是,漏收回調不等於漏掉付款:付款無論如何都已記錄,事件也可以重新取回。

這份清單逐步説明如何找出收不到回調的原因、每種原因怎麼修,以及如何補回所有漏掉的事件。

第一步:回調到底發出去了嗎?

在排查你的服務器之前,先看平台這一側。在控制台 開發者 → Webhook 的 Webhook 投遞台賬 裏,可以看到近 30 天每一次投遞的事件、關聯單號、嘗試次數 和 最近結果:

投遞記錄顯示含義下一步看哪裏
這個事件完全沒有投遞沒有任何端點訂閲它「有沒有訂閲這個事件?」
無法連接地址沒有響應:DNS、TLS、防火牆,或服務器宕機「回調地址能從公網訪問嗎?」
你的接口返回 HTTP 4xx/5xx請求到了,但你的服務器拒絕了或處理出錯「你的處理代碼拒絕了它嗎?」
你的接口返回 HTTP 3xx你的服務器做了重定向,按失敗處理「回調地址能從公網訪問嗎?」
等待重試投遞失敗,已安排下一次重試修好原因,重試時會自動送達
重試已耗盡6 次嘗試全部失敗修好原因後點擊 立即重投
已送達你的接口按時返回了 2xx問題出在收到之後你自己的處理流程

開發者 → 事件 頁面從事件的角度展示同樣的信息:每個事件是 已送達、重試中、有幾個地址投遞失敗,還是因為沒有配置接收地址而 無投遞。

有沒有訂閲這個事件?

  • 既沒有端點,也沒有 notify_url。 下單時沒傳 notify_url 的訂單,會投遞給它所屬應用中訂閲了該事件的 Webhook 端點(以及賬戶級端點)。一個都沒有,就什麼都不會發送。正式訂單不傳 notify_url 時,若沒有端點訂閲 payment.succeeded,創建時就會被拒絕;但退款、拒付等其他事件,仍然需要單獨訂閲。
  • 訂閲錯了事件。 每個端點都有自己的事件類型列表。只訂閲 payment.succeeded 是常見的坑:這樣你永遠收不到 payment.failed、payment.expired、退款和拒付的通知。見 事件類型。
  • 環境或應用不對。 沙箱和正式環境的端點相互獨立,端點也屬於某個應用。用測試密鑰創建的訂單永遠不會投遞到正式端點,一個應用的訂單也不會投遞到另一個應用的端點。
  • 訂單級 notify_url 優先。 如果訂單創建時傳了 notify_url,這個訂單的事件會發到該地址,而不是你的端點。排查端點之前,先看一下訂單本身。

回調地址能從公網訪問嗎?

  • 必須是公網 HTTPS 地址。 localhost、內網地址或 HTTP 地址在下單時就會被拒絕;本地調試可以用 cloudflared 或 ngrok 暴露一個公網地址。
  • 填處理接口,不要填首頁。 平台不跟隨重定向,3xx 按失敗處理,所以處理接口前面的 http → https 或 www 跳轉都會讓投遞失敗。請填寫最終的地址。
  • 讓請求能通過。 防火牆、WAF 規則、機器人防護或接口前的 HTTP 基本認證,都會把回調變成一個攔截頁面。
  • 改好地址就能救回隊列。 還在排隊的重試和手動重投,都會發到端點當前的地址,所以改正地址就能挽救排隊中的投遞。(訂單級 notify_url 保持下單時傳入的值不變。)

你的處理代碼拒絕了它嗎?

  • 驗簽失敗。 必須用原始請求體驗簽,在任何 JSON 解析或重新序列化之前;解析後再編碼的請求體就對不上了。密鑰輪換期間,X-KukoPay-Signature 請求頭裏會有多個 v1= 簽名,只要有一個匹配就是合法請求。用 Object.fromEntries 之類的寫法解析請求頭只會保留最後一個,導致這 24 小時內的投遞全部驗簽失敗。
  • 時間戳校驗太嚴,或者服務器時鐘不準。只拒絕超過 5 分鐘的時間戳,並保持時鐘同步。
  • 遇到沒預料到的事件類型就崩潰。 對不處理的事件類型也返回 2xx,不要拋出異常。
  • 用驗簽自檢工具。 Webhook 頁面上的 驗簽自檢 可以粘貼收到的原始請求體和簽名頭,告訴你問題出在密鑰、請求體被重新格式化,還是時間戳。

一個正確驗簽並去重的處理方式,見 Webhook 重複通知怎麼辦。

是不是超時了?

只有你的接口在 10 秒內返回 2xx,投遞才算成功。如果處理代碼在響應之前調用了緩慢的發貨接口、發送郵件或生成發票,負載一高就會超時,而且在它還沒處理完的時候就被重投。先響應,後處理:驗簽、記錄事件、返回 2xx,再把耗時的工作交給隊列。

Webhook 是怎樣重試的?

投遞失敗會按以下間隔重試,連同第一次共 6 次:

首次發送 → 30 秒 → 2 分鐘 → 10 分鐘 → 30 分鐘 → 2 小時

  • 每次重試的事件 id 都不變,請用它去重。
  • 每個端點各自重試,一個端點故障不會拖住其他端點。
  • 第 6 次失敗後不再自動重試。修好原因後,在投遞記錄裏點擊 立即重投。

如何補回漏掉的 Webhook 事件

即使所有投遞都失敗了,甚至從來沒有配置過端點,事件仍然在。在 KukoPay,事件先記錄、後投遞,所以 事件接口 就是補回的途徑:

# 拉回昨天所有支付成功事件
curl -G "https://api.kukopay.com/v1/events" \
  -H "X-Api-Key: $KUKOPAY_API_KEY" \
  -d type=payment.succeeded \
  -d "created[gte]=2026-11-03T00:00:00Z" \
  -d "created[lte]=2026-11-03T23:59:59Z" \
  -d limit=100

Webhook 的請求體就是事件對象,所以補回的事件直接交給同一套處理代碼即可。因為你的處理代碼按事件 id 去重,重放已經處理過的事件也不會有任何影響。投遞記錄保留 30 天,事件本身不會被清理。

如果只關心一筆訂單,直接查詢訂單:只有 status: "paid" 才表示已經付款。

端到端測試 Webhook

  • 發送測試回調(在 開發者 → 沙箱聯調 中)會向一個端點或地址發送正確簽名的請求,並顯示發出的內容和你的服務器返回的結果。它的 id 以 evt_test_ 開頭,帶 "test": true。
  • 對沙箱訂單使用 模擬結果,不打開收銀台就能走完真實流程:事件、投遞和重試。
  • 讓接收端第一次返回 500,觀察重試是否帶着相同的事件 id 到達。

這些和其他沙箱場景一起列在 支付測試卡號大全 裏。

不要讓一次漏收的回調決定訂單結果

排查期間請記住,訂單並沒有丟:對賬任務會持續向通道核對待支付的付款,而買家的回跳無論如何都不是結果,見 return_url 和 notify_url 的區別。不要因為沒收到回調就把訂單標記為失敗;去查詢訂單,或者補回事件。

總結

  • 先看投遞記錄:沒有投遞、無法連接、HTTP 錯誤或重試已耗盡,分別指向不同的原因;
  • 檢查事件訂閲、環境、應用,以及訂單級 notify_url;
  • 使用直接響應的公網 HTTPS 地址,中間不要有重定向或防火牆攔截;
  • 用原始請求體驗簽,輪換期間接受任意一個 v1=,並在 10 秒內返回 2xx;
  • 一次失敗的投遞在約 2 小時 45 分鐘內共嘗試 6 次,之後使用 立即重投;
  • 漏掉的事件都可以從事件接口補回:即使投遞失敗,事件也已經記錄。

在沙箱裏把它跑通

註冊商戶後即可申請測試密鑰,不需要等待資質審核。