ワークフローを組み、Listen for test event(テストイベントを待ち受ける)をクリックしたときは問題なく動きました。その URL を Stripe に貼り付けたのに、何も起きません。実行もエラーも記録されていません。プロバイダーは配信に失敗したと表示しています。もっと悪い場合は成功したと表示しているのに、手元には何も届いていません。
原因はわずかなのに、迷う人は後を絶ちません。主な理由は 2 つあります。n8n の失敗メッセージが目立たないこと、そしていちばん多い原因が一見正しく見える URL であることです。以下、実際に原因になっている頻度の高い順に並べます。
TL;DR
- ほとんどの場合、答えはテスト URL と本番 URL の取り違えです。テスト URL は画面を見ている間しか受信しません
- 本番 URL は、ワークフローを有効化して初めて存在します
- 応答モードの既定は、すぐに応答する設定です。ほとんどのプロバイダーではそれで問題ありませんが、チャレンジ値を返す必要がある場合は命取りになります
- 署名検証には Raw Body(生のボディ)のオンが必須です。オフのままでは HMAC は決して一致しません
- 実行が失敗すると、イベントは取り残されます。n8n は受信した Webhook をリトライしません
- リバースプロキシの背後でセルフホストすると、見落とされがちなボディサイズとタイムアウトの層が加わります
1. テスト URL を使っている(ほとんどはこれ)
n8n の Webhook ノードには URL が 2 つ表示され、その動作はまったく違います。
Test: https://your-instance/webhook-test/abc-123
Production: https://your-instance/webhook/abc-123
テスト URL が有効なのは、Listen for test event をクリックしてキャンバスを見ている間だけです。実行を 1 回記録してデータを表示し、そこで止まります。画面を離れた瞬間から、何が来ても 404 を返します。
開発中の設計としてはまさに正しく、Stripe に貼り付ける URL としてはまさに間違いです。しかも開発中は完璧に動いたので、「Webhook は動いている」という思い込みができあがります。それが本当の原因を見えにくくします。
プロバイダーに登録されている URL を確認してください。webhook-test が含まれていれば、それがバグです。ここで読むのをやめてかまいません。
2. ワークフローが有効化されていない
本番 URL は、ワークフローを有効化(公開)したときに登録されます。無効なワークフローの本番 URL は 404 を返します。サーバーから見れば、そのルートは存在しないからです。
落とし穴は、保存しても有効化にはならないことです。ワークフローを一日中編集して保存し、閉じても、本番のルートは一切更新されません。同じ系統の失敗には次のようなものがあります。
- 変更のために無効化し、そのまま再有効化を忘れるケース。その間に届いたイベントはすべて失われます
- 変更を試すためにワークフローを複製するケース。複製は無効のままで、トラフィックを受けているのは元のワークフローです
- Webhook ノードのパスを変更するケース。パスを変えると URL も変わりますが、プロバイダーは古い URL を向いたままです
エディターのトグルを確認してください。あわせて、プロバイダー側の URL が現在のパスと完全に一致しているかも確認します。
3. 応答モードがプロバイダーの期待と合っていない
Webhook ノードの Respond(応答)設定、つまり応答モードは、n8n がいつ、何を返すかを決めます。
| モード | 動作 |
|---|---|
| Immediately(即時) | イベントが届いた時点で、「Workflow was started」というメッセージとともに 200 を返却 |
| When Last Node Finishes(最後のノードの完了時) | ワークフローが完了するまで接続を保持し、最後のノードの出力を返却 |
| Using 'Respond to Webhook' node | ステータスとボディを自分で明示的に制御 |
既定値の Immediately が正解で、基本はこのままにしておくべきです。すぐに応答するので、ワークフローにどれだけ時間がかかっても、どのプロバイダーのタイムアウトにも収まります。
これが不適切なケースが 2 つあります。
1 つ目は検証のハンドシェイクです。一部のプロバイダーは、登録時にトークンやボディのハッシュなどのチャレンジ値をそのまま返すよう求めます。Immediately は固定のメッセージを返すので、登録は拒否されます。期待される値を正確に返すには、Respond to Webhook ノードが必要です。
2 つ目は、遅いワークフローで When Last Node Finishes を使う場合です。応答時間が、ワークフロー全体の実行時間そのものになります。API 呼び出し 3 回と Google Sheets への書き込みで、8 秒はすぐにかかります。Shopify の応答期限は 5 秒です。送信元はタイムアウトを記録してリトライし、ワークフローは最初からもう一度実行されます。重複した行を黙って作るのは、このモードです。
When Last Node Finishes を使っていて、レスポンスに出力を含める必要が特にないなら、Immediately に切り替えてください。
4. Raw Body がオフで署名検証に失敗する
n8n の中で Stripe、Shopify、GitHub の署名を検証するなら、Webhook ノードの Raw Body オプションは必須です。
プロバイダーは、送信したバイト列そのものに対して HMAC を計算します。Raw Body がオフだと、n8n は JSON をパースします。それを Code ノードで再シリアライズすると、キーの順序や空白が変わり、別のバイト列になります。データは同じでも、署名は一致しません。見るからに正しいペイロードを前に、検証だけが失敗し続けます。本当に頭を抱える 1 時間になります。
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. 実行が失敗するとイベントが取り残される
実際にお金の損失につながるのはこれです。バグではありません。そういう仕組みなのです。
Respond が Immediately のとき、n8n はワークフローが動く前に 200 を返し終えています。送信元はイベントを配信済みとみなし、二度と送りません。その後 4 番目のノードが、期限切れのトークン、レート制限、不正な形式のフィールドなどで失敗すると、その実行は失敗として記録されます。イベントは、その失敗した実行の記録の中にしか残りません。
受信した Webhook が自動でリトライされることはありません。手前にキューもありません。送信元から見ればすべて正常に終わっているので、送り直されることもありません。
対策は次のとおりです。
- ワークフローの設定で Error Workflow(エラーワークフロー)を指定します。最低でも通知が届くようにしてください。黙って失敗するのが既定の動作で、これは良い既定値ではありません
- 1 番目のノードで生のペイロードを永続化します。変換の前に、Postgres、Airtable、Google Sheets など、消えない場所ならどこでもいいのでボディを書き込みます。そうすれば失敗した実行も失われず、自分のストアから再送できます
continueOnFailは、部分的な成功を許容できるノードにだけ意図して使います。気づかなければならない失敗を黙って握りつぶすノードには使いません- 保存期間を確認します。セルフホストの n8n は実行データを定期的に削除し、削除された後は失敗した実行のペイロードも残りません
6. セルフホスト:リバースプロキシが邪魔をしている
nginx、Traefik、Caddy の背後でセルフホストしている場合、n8n に届く前にリクエストを飲み込んでしまう層がいくつかあります。
- ボディサイズの上限。nginx の
client_max_body_sizeの既定値は 1MB で、それより大きいものには413を返します。Shopify の一括処理のペイロードや base64 の添付ファイルはこれを超えます - プロキシのタイムアウト。
proxy_read_timeoutがワークフローの実行時間より短いと、プロキシが接続を閉じます。n8n の処理は完了していても、送信元には失敗として記録されます WEBHOOK_URLが未設定。n8n は画面に表示する URL をこの値から生成します。プロキシの背後では、手元のノート PC からは届くのにインターネットからは届かない内部アドレスが表示されます- キューモードのワーカー。キューモードでは Webhook がメインインスタンスに届く必要があり、ワーカーにルーティングすると 404 になります
手早く診断するには、ネットワークの外から本番 URL に curl -X POST を送ります。404 ならルーティングか有効化の問題です。413 ならボディサイズ、応答が返ってこなければタイムアウトです。200 が返るのに Executions(実行履歴)に何もなければ、実行が保存されていません。ワークフローの設定で、本番実行の保存に関する項目を確認してください。
7. 届いているのに、フィルターで落とされている
頻度は低いものの、確認しておく価値があります。実行は存在するのに、何もしていないように見えるケースです。IF ノードや Switch ノードの条件を、実際のペイロードの構造と照らし合わせてください。プロバイダーのネストの仕方は、開発時に使ったテスト用のペイロードとは違うからです。実行がまったくない場合は、ノードの HTTP Method(HTTP メソッド)を確認してください。既定値は GET で、GET しか受け付けない Webhook にプロバイダーが POST すると 404 が返ります。これはルーティングの問題とまったく同じに見えます。
AnyHook の位置づけ
構造的な問題は原因 5 です。n8n はワークフローを実行するものです。先にイベントを保存するわけではなく、200 を返した時点で、ほかにコピーを持っていたのはプロバイダーだけです。
AnyHook を前段に置くと、「配信済み」の意味が変わります。プロバイダーの送信先を in.anyhook.net/you/app にし、AnyHook の転送先を n8n の本番 URL にします。変えるのは URL 2 つだけで、ワークフローの書き換えは要りません。そこから先は次のようになります。
- n8n を呼び出す前にイベントが永続化されるので、失敗した実行もログから再送できます
- n8n インスタンスへのリトライは指数バックオフで行われるので、アップグレード中に再起動しても自動で復旧します
- 署名検証はエッジで行われます。プロバイダーのシークレットを一度設定すれば、受け取るのは
AnyHook-Signatureヘッダー 1 つだけです。原因 4 の Raw Body をめぐる手間は、どのプロバイダーにも使える HMAC チェック 1 つになり、それを書くのも一度で済みます - 失敗アラートは、連続失敗が 1 回、5 回、20 回に達したときに送られます。自動の一時停止もあるので、壊れたワークフローにリトライが殺到することはありません
実際に効いてくるのは、こういう場面です。n8n のアップグレードでインスタンスが 10 分間ダウンすると、その 10 分間のイベントをすべて失いかねません。前段に中継サービスがあれば、かかるのは再送のクリック 1 回だけです。
設定にコードは要りません。
- AnyHook で App(Webhook の送信元ごとに作る受信設定)を作成します。送信元が一覧にあれば(Shopify はあります)それを選び、Typeform のように一覧にないものなら Generic(汎用)を選びます。転送先には、ワークフローの Production URL(本番 URL)を設定します。
- 送信元の設定で、Webhook URL を App の受信 URL に置き換えます。Shopify の管理画面では Settings → Notifications → Webhooks、Typeform では Connect → Webhooks です。
- ワークフローが n8n 独自の Shopify Trigger ノードや Typeform Trigger ノードで始まっている場合は、通常の Webhook ノードに置き換えます。Trigger ノードはプロバイダーに独自の Webhook を登録するので、AnyHook を経由しなくなります。
- 障害の後は、AnyHook で App を開き、失敗したイベントのバナーから再送します。
1 つの App が受け持つ送信元は 1 つで、Free プランで作れる App は 1 つです。2 つ目の送信元には Pro プランが必要です。
まとめ
まず URL を確認してください。答えは webhook-test と webhook の違いであることが群を抜いて多く、僅差の 2 位が有効化です。受信できるようになったら、忘れないうちに永続化を整えます。最初のノードで生のペイロードを書き込み、エラーワークフローを設定してください。n8n は取りこぼしたイベントを送り直しませんし、プロバイダーも送り直しません。