自動化2026年8月3日閱讀時間 8 分鐘

n8n Webhook 沒反應:七個原因,依實際出現的頻率排序

十次有九次是 Test URL:它只在你看著畫面時接收。其餘原因是回應模式、原始請求主體已被解析,或事件遺失。

/webhook-test/… · 只監聽一個事件你按一下Listen for test1 次執行你離開頁面404對所有請求永遠/webhook/… · 只在啟用時存在工作流程已儲存,未啟用404啟用路由上線已儲存 ≠ 已啟用
兩個看起來幾乎一樣的網址,行為完全不同。多數時候,答案就是這個。

你建好了工作流程,按下「Listen for test event」(監聽測試事件)時跑得很順,把網址貼進 Stripe 之後,卻什麼都沒發生。沒有執行紀錄,也沒有錯誤。服務商說傳遞失敗;更糟的是,它說傳遞成功,你這邊卻什麼都沒有。

原因就那幾個,卻能讓人困惑很久。主要是因為 n8n 出錯時很安靜,而最常見的原因,是一個看起來完全正確的網址。以下依它們實際造成問題的頻率排列。

重點摘要

  • 多數時候,答案是 Test URL(測試網址)和 Production URL(正式網址)的差別。Test URL 只在你看著它時才接收。
  • Production URL 要等工作流程啟用後才存在。
  • 回應模式預設是立刻回覆。這對多數服務商沒問題,但如果你需要回傳 challenge,就是致命傷。
  • 任何簽章驗證都必須開啟 Raw Body(原始請求主體),否則 HMAC 永遠對不上。
  • 執行失敗,事件就擱淺了。n8n 不會重試收到的 Webhook。
  • 自架在反向代理後面,會多一層請求主體大小和逾時的限制,很容易被忘記。

1. 你用的是 Test URL(多半就是這個)

n8n 的 Webhook 節點會顯示兩個網址,兩者的行為完全不同:

Test:        https://your-instance/webhook-test/abc-123
Production:  https://your-instance/webhook/abc-123

Test URL 只在你按下「Listen for test event」、而且停在畫布上時才有效。它只記錄一次執行,把資料顯示給你看,然後就停止。你一離開這個畫面,它對所有請求都回 404。

開發時,這正是對的設計;拿來貼進 Stripe,就完全錯了。而且因為開發期間它一切正常,你心裡會認定「Webhook 是好的」,真正的原因反而更難看出來。

/webhook-test/… · 只監聽一個事件你按一下Listen for test1 次執行你離開頁面404對所有請求永遠/webhook/… · 只在啟用時存在工作流程已儲存,未啟用404啟用路由上線已儲存 ≠ 已啟用

去看看你在服務商那邊登記的網址。如果裡面有 webhook-test,問題就是這個,後面不用再看了。

2. 工作流程沒有啟用

Production URL 要在工作流程啟用(發布)時才會註冊。未啟用的工作流程,它的 Production URL 會回 404,因為對伺服器來說,這個路由根本不存在。

陷阱在於:儲存不等於啟用。你可以整天編輯、儲存、關閉工作流程,正式路由卻從來沒有更新。同一類的相關錯誤還有:

  • 為了修改先停用,改完卻忘了重新啟用。這段期間的每個事件都沒了。
  • 複製一份工作流程來測試修改。副本沒有啟用,實際接收流量的仍是原本那份。
  • 改了 Webhook 節點的路徑,網址也跟著改變。服務商卻還指向舊網址。

檢查編輯器裡的啟用開關,也確認服務商那邊的網址和目前的路徑完全一致。

3. 回應模式不符合服務商的預期

Webhook 節點的 Respond(回應)設定,也就是它的回應模式(response mode),決定 n8n 什麼時候回覆、回覆什麼:

模式行為
Immediately(立即)事件一到就回 200,內容是「Workflow was started」
When Last Node Finishes(最後節點完成時)連線一直保持到工作流程跑完,回傳最後一個節點的輸出
Using 'Respond to Webhook' node(用 Respond to Webhook 節點)由你明確指定狀態碼和回應主體

Immediately 是正確的預設值,你應該維持這個設定。它回得快,不管工作流程跑多久,都落在每個服務商的回應時限內。

有兩種情況它不適用。

驗證握手。有些服務商在訂閱時,要求你原樣回傳一個 challenge 值,例如一個 token 或請求主體的雜湊。「Immediately」只會回固定訊息,訂閱就會被拒絕。你需要用 Respond to Webhook 節點,回傳對方預期的確切值。

When Last Node Finishes 搭配很慢的工作流程。這時你的回應時間,就是整個工作流程的執行時間。三次 API 呼叫加一次 Google Sheets 寫入,很容易就要 8 秒,而 Shopify 的回應時限是五秒。服務商記下一次逾時並重試,你的工作流程就從頭再跑一遍。悄悄產生重複資料列的,就是這個模式。

如果你用的是「When Last Node Finishes」,又不是非得在回應裡帶上輸出,就改成 Immediately。

4. Raw Body 沒開,所以簽章驗證失敗

如果你在 n8n 裡驗證 Stripe、Shopify 或 GitHub 的簽章,Webhook 節點的 Raw Body 選項一定要開。

服務商是對它送出的確切位元組計算 HMAC。Raw Body 沒開時,n8n 會解析 JSON,之後任何把它重新序列化的 Code 節點,產生的位元組都不一樣:鍵的順序不同,空白也不同。資料一模一樣,簽章卻對不上。你會盯著一份看起來完全正確的 payload,驗證卻一直失敗,這一個小時真的會讓人抓狂。

開啟 Raw Body,然後在解析任何東西之前,先對原始字串計算 HMAC:

const crypto = require('crypto');
const raw = $input.first().binary
  ? Buffer.from($input.first().binary.data.data, 'base64').toString('utf8')
  : $input.first().json.body;

const expected = crypto
  .createHmac('sha256', $env.SHOPIFY_SECRET)
  .update(raw, 'utf8')
  .digest('base64');

const received = $input.first().json.headers['x-shopify-hmac-sha256'];
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
  throw new Error('invalid signature');
}
return { json: JSON.parse(raw) };

用 timingSafeEqual,不要用 ===。另外要注意,長度不一致時它會拋出例外,如果輸入來源不可信,要先擋掉這種情況。各服務商的簽章格式,詳見驗證 Stripe、GitHub 和 Shopify 的 Webhook 簽章。

5. 執行失敗,事件就擱淺了

真正會讓你賠錢的就是這一個,而且它不是 bug,只是這套模型本來就這樣運作。

Respond 設成 Immediately 時,n8n 在你的工作流程開始跑之前,就已經回了 200。服務商認定事件已經送達,不會再送一次。接著如果第 4 個節點因為 token 過期、觸發速率限制或欄位格式錯誤而失敗,這次執行會被標為失敗,而這個事件除了那筆失敗的執行紀錄,哪裡都找不到。

收到的 Webhook 不會自動重試,前面也沒有佇列。服務商不會重新傳送,因為從它的角度看,一切都很順利。

該怎麼處理:

  • 在工作流程設定裡指定一個 Error Workflow(錯誤處理工作流程)。至少要讓它通知你。預設是安靜地失敗,這不是好的預設。
  • 把保存原始 payload 當成第 1 個節點。在任何轉換之前,先把請求主體寫進 Postgres、Airtable、Google Sheet,任何能持久保存的地方都可以。這樣失敗的執行就能從你自己的儲存重播,而不是就此遺失。
  • 只在可以接受部分成功的節點上刻意使用 continueOnFail。如果它會悄悄吞掉你需要看到的失敗,就不要用在那個節點上。
  • 檢查保存期限。自架的 n8n 會定期清除執行資料,清掉之後,連失敗執行的 payload 也沒了。

6. 自架環境:反向代理擋在前面

如果你自架在 nginx、Traefik 或 Caddy 後面,有幾層可能在 n8n 看到請求之前就把它吃掉:

  • 請求主體大小上限。nginx 的 client_max_body_size 預設是 1MB,超過就回 413。Shopify 的大量 payload 或 base64 編碼的附件都會超過。
  • 代理逾時。如果 proxy_read_timeout 比你的工作流程短,代理會先關閉連線,服務商記下一次失敗,即使 n8n 其實已經跑完。
  • 沒有設定 WEBHOOK_URL。n8n 顯示的網址是用它產生的。在代理後面,它會顯示內部位址,從你的筆電連得到,從網際網路卻連不到。
  • 佇列模式(queue mode)的 worker。在佇列模式下,Webhook 必須送到主執行個體,轉到 worker 會得到 404。

最快的診斷方式:從你的網路外部,對 Production URL 執行 curl -X POST。回 404,是路由或啟用的問題。回 413,是請求主體大小。一直沒有回應,是逾時。回 200 但 Executions(執行紀錄)裡什麼都沒有,代表這次執行沒有被儲存:到工作流程的設定裡,檢查儲存正式環境執行紀錄(production executions)的選項。

7. 事件有到,卻被篩選條件丟掉

比較少見,但值得排除:執行紀錄存在,看起來卻什麼都沒做。拿真實的 payload 結構,比對 IF 或 Switch 節點的條件,因為服務商的巢狀結構,常和你開發時用的測試 payload 不一樣。如果根本沒有執行紀錄,就檢查節點的 HTTP Method(請求方法),它預設是 GET。服務商用 POST 打一個只收 GET 的 Webhook,會得到 404,看起來和路由問題一模一樣。

AnyHook 的角色

第 5 個原因是結構性的。n8n 負責執行工作流程,它不會先儲存事件。它一回 200,除了這次執行,唯一的另一份副本就在服務商手上。

在前面放一層 AnyHook,「已送達」的意思就不一樣了。把服務商指向 in.anyhook.net/you/app,再把 AnyHook 指向你的 n8n Production URL:只改兩個網址,不用改寫工作流程。之後:

  • 呼叫 n8n 之前,事件就已經儲存,所以失敗的執行可以從事件紀錄重播。
  • 對 n8n 執行個體的重試採用指數退避,所以升級期間重新啟動,也會自己恢復。
  • 簽章驗證在邊緣節點完成。服務商的密鑰只要設定一次,收到的就是單一的 AnyHook-Signature 標頭。第 4 個原因裡那套 Raw Body 的繁瑣步驟,就變成一個 HMAC 檢查,寫一次,所有服務商通用。
  • 連續失敗 1、5、20 次時發出失敗警示,並自動暫停,避免壞掉的工作流程引發重試風暴。

實務上真正重要的是這一點:一次讓執行個體停機十分鐘的 n8n 升級,可能讓你損失這十分鐘裡的每個事件。前面有一層 Webhook 中繼服務(webhook relay),代價就只是按一下重播。

設定完全不用寫程式:

  1. 在 AnyHook 建立一個 App(一個接收網址,對應一個 Webhook 來源)。如果發送端在清單上(Shopify 有),就選它當來源;不在清單上的,例如 Typeform,就選 Generic。目的地設成工作流程的 Production URL。
  2. 在發送端,把 Webhook 網址換成這個 App 的接收網址(inbound URL)。在 Shopify 管理介面,路徑是「設定 > 通知 > Webhook」(Settings → Notifications → Webhooks);在 Typeform 是 Connect → Webhooks。
  3. 如果工作流程是從 n8n 內建的 Shopify 或 Typeform Trigger 節點開始,把它換成一般的 Webhook 節點。Trigger 節點會自己向服務商註冊 Webhook,這樣就會繞過 AnyHook。
  4. 服務中斷之後,在 AnyHook 打開這個 App,從失敗事件的橫幅重播。

一個 App 對應一個發送端,Free 方案可以建一個 App,所以第二個發送端就需要 Pro 方案。

結論

先檢查網址。webhook-test 和 webhook 的差別,遠比其他原因更常是答案,緊追在後的是工作流程沒啟用。收得到事件之後,趁還記得,先處理持久保存:第一個節點就寫入原始 payload,再設定 Error Workflow。n8n 不會重送它丟掉的東西,服務商也不會。

所有文章2026年8月3日 · 8 分鐘

別再漏掉 Webhook。

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