當 webhook 被多次觸發時,我該如何防止工作流程重複執行?

當 webhook 被觸發多次時,我如何防止工作流程重複執行?

錯誤訊息是什麼(如果有的話)?

Webhook Set HTTP Request Airtable 我知道 Stripe 會重試失敗的請求,但即使成功的請求有時似乎也會到達兩次。我如何確保每個事件只被處理一次?

請分享您的工作流程

(在畫布上選擇節點,並使用鍵盤快速鍵 CMD+C/CTRL+C 和 CMD+V/CTRL+V 來複製和貼上工作流程。)

分享最後一個節點返回的輸出

我正在使用接收來自 Stripe 事件的 Webhook 節點。有時,同一事件被傳遞多次,導致我的工作流程處理重複的訂單

關於您的 n8n 設定的資訊

  • n8n 版本:1.123
  • 資料庫(預設值:SQLite):PostgreSQL
  • n8n EXECUTIONS_PROCESS 設定(預設值:own, main):
  • 透過以下方式執行 n8n(Docker、npm、n8n cloud、桌面應用程式):
  • 作業系統:

Hi @Fatoki_Alfred

每個 Stripe 事件都包含一個唯一的 id(例如 evt_1Nabc...)。為確保每個事件只被處理一次,你應該在 Postgres 資料庫中的專用「已處理事件」表中追蹤這些 ID。

Hi @Fatoki_Alfred
n8n 有一個內建的 Remove Duplicates 節點可以做到這一點,不需要自訂表格或查詢。在 Webhook 節點後直接放入它,並將 Operation 設為「Remove Items Processed in Previous Executions」、Keep Items Where 設為「Value Is New」,然後將 Value to Dedupe On 設為 Stripe 事件 id:

{{ $json.body.id }}

n8n 會將看過的 id 歷史記錄保存在自己的 Postgres 資料庫中,所以在重新啟動後仍會保留,任何重複傳遞都會在到達你的訂單步驟前被丟棄。

補充上述回答:容易被忽略的要點是競態條件(race condition)。如果 Stripe 並行傳遞相同的 evt_id,兩個工作流可能在任何一個記錄該 id 之前都通過了重複排除檢查——兩個都會繼續執行。Remove Duplicates 節點解決了序列情況,但在並發下不是原子性的。

要真正防護,在資料庫層面進行重複排除:建立帶有 UNIQUE 約束的欄位(例如:stripe_event_id TEXT UNIQUE),然後在 Webhook 之後加一個 Postgres 節點執行 INSERT … ON CONFLICT (stripe_event_id) DO NOTHING RETURNING id。如果 RETURNING 為空,就是重複交付 → 在那裡停止流程(用 IF 檢查是否有返回行)。資料庫保證原子性,即使兩個交付同時到達:也只有一個會成功完成 INSERT。

兩個額外措施可以大幅減少源頭問題:(1) 盡快向 Stripe 回應 200(使用 Webhook 在 Respond Immediately 模式或在開頭放一個 Respond to Webhook 節點)——超時會導致 Stripe 重試,這就是很多「重複」的來源;(2) 只在控制 INSERT 通過後才處理訂單。這樣事件 id 就是你真正的冪等性金鑰,訂單邏輯永遠不會運行兩次。

@Fatoki_Alfred

Stripe 會刻意重試 webhook 傳遞,因此重複事件是預期的行為。建議的做法是讓你的工作流程具有冪等性,而不是假設每個 webhook 都是唯一的。
最簡單的解決方案是在處理前儲存 Stripe 的 event.id。在工作流程開始時:

  • 提取 event.id。
  • 查詢資料庫(或 Data Store)中的該 ID。
    如果已存在,停止工作流程。
    否則儲存 ID 並繼續處理。
    在業務邏輯前使用 IF 節點通常就足夠了。
    如果你要寫入 Airtable 或其他資料庫,考慮將 event.id 設為唯一欄位,這樣重複項目就會自動被拒絕。

將 Stripe webhooks 視為至少一次送達,並使工作流程具有冪等性。使用 Stripe 事件 ID 作為冪等性金鑰。在工作流程開始時,驗證 webhook 簽名,並嘗試將該事件 ID 插入到具有唯一約束的 PostgreSQL 表中。只有在插入成功時才繼續。如果 ID 已存在,則返回成功回應並停止,不再次建立訂單。

一個簡單的表可以包含 event_id 作為主鍵,加上 status、received_at 和 completed_at。在 Postgres 節點中使用帶有 ON CONFLICT DO NOTHING 的插入,並返回是否插入了一行。將該結果發送到 IF 節點。true 分支處理訂單,false 分支退出。資料庫約束很重要,因為兩份副本可能到達得足夠接近,以至於單獨的查詢後插入檢查會讓兩者都通過。

在聲稱時將記錄標記為處理中,只有在訂單成功後才標記為已完成。決定失敗的記錄應如何重試,以便臨時錯誤不會永久禁止該事件。同時將 Stripe 事件 ID 傳遞到支持的下游系統作為其冪等性金鑰。不要按客戶、金額或時間戳進行重複刪除,因為獨立的合法付款可能共享這些值。

從穩定的事件 ID 建立冪等金鑰,並在昂貴的節點執行之前儲存它。如果相同的金鑰再次到達,就提前返回並設定與發送者可能重試時長相符的過期時間。