初心者向け

n8n Webhookノードの使い方:URLが2つある理由と、テストで詰まる場所

Webhookノードには Test URL と Production URL があり、有効になる条件が違います。404になる3つのパターン、テスト用ボタンが2つあって実行範囲が変わること、届くデータの形を実際に叩いて確かめました。

動作確認: — n8n 2.36.8 / Webhook ノード v2.1

Webhook ノードは、外部から n8n を呼び出すための受け口です。 フォームの送信、決済の完了、GitHub のプッシュ。そういった「向こうから来る」きっかけで ワークフローを動かすときに使います。

設定項目は少ないのに、動かなくて詰まることが多いノードでもあります。 理由ははっきりしていて、URLが2種類あり、それぞれ有効になる条件が違うからです。

実際に叩いて確かめた内容をまとめます。

URLが2つある

ノードを開くと、上に Webhook URLs という欄があり、Test URL と Production URL の 切り替えタブが出ます。

n8nのWebhookノード設定画面。Webhook URLs欄でTest URLタブが選ばれ、POST http://localhost:5678/webhook-test/hook-probe と表示されている。下にHTTP Method、Path、Authentication、Respondの各項目が並ぶ
Test URL を選んだ状態。パスに webhook-test が入ります
同じ画面でProduction URLタブを選んだ状態。POST http://localhost:5678/webhook/hook-probe と表示され、webhook-test ではなく webhook になっている
Production URL は webhook。1文字違いではなく、別のパスです

見た目はよく似ていますが、パスが違います。

Test URL Production URL
パス /webhook-test/<Path> /webhook/<Path>
有効になる条件 ボタンを押して待機状態にしたとき Publish 済みのとき
使える回数 1回だけ 何度でも
画面での見え方 キャンバスにデータが流れる キャンバスには出ない(実行履歴にだけ残る)

Publish しても Test URL は有効になりません。 逆に、待機状態にしても Production URL が有効になるわけでもありません。この2つは完全に別系統です。

404になる3つのパターン

叩いて404が返るとき、n8n はどれに該当するかを教えてくれます。

1. Publish していないのに Production URL を叩いた

{
  "code": 404,
  "message": "The requested webhook \"POST hook-probe\" is not registered.",
  "hint": "The workflow must be active for a production URL to run successfully. ...
           Note that unlike test URL calls, production URL calls aren't shown on the canvas
           (only in the executions list)"
}

2. 待機状態にしていないのに Test URL を叩いた

{
  "hint": "Click the 'Execute workflow' button on the canvas, then try again.
           (In test mode, the webhook only works for one call after you click this button)"
}

3. メソッドが違う

{
  "message": "This webhook is not registered for GET requests.
              Did you mean to make a POST request?"
}

HTTP Method を POST にしてあるのに GET で叩くと、これが返ります。 パスが合っていても404になるので、URLばかり見ていると気づきません。

テスト用のボタンが2つあり、動く範囲が違う

ここが一番はまりました。

Test URL を有効にするボタンは、2箇所にあります。

  • ノードを開いた中にある Listen for test event
  • キャンバスの下にある Execute workflow

どちらを押しても Test URL は有効になります。ただし、動く範囲が違いました。

同じ Test URL を、同じ内容で叩いた結果です。

押したボタン 実行された範囲 返ってきた応答
Listen for test event(ノード内) Webhookノードだけ 空・200
Execute workflow(キャンバス) ワークフロー全体 201 + {"ok":true,...}

ノード内のボタンで実行したときの記録を見ると、こうなっていました。

"startData": { "destinationNode": { "nodeName": "受け口", "mode": "inclusive" } },
"lastNodeExecuted": "受け口"

そのノードまでで止める指定が入っています。 後続のノードには実行記録が1つもありません。

つまり Listen for test event は「このノードに何が届くか」を見るためのもので、 ワークフロー全体の動作確認には使えません。応答が空で返ってきたら、 設定ミスではなく押したボタンを疑ってください。

n8n のエラーメッセージが案内しているのも Execute workflow の方です。

届くデータの形

Production URL に、クエリとカスタムヘッダーを付けて叩いた結果です。

{
  "headers": {
    "host": "localhost:5678",
    "user-agent": "curl/8.21.0",
    "content-type": "application/json; charset=utf-8",
    "x-custom-header": "hello"
  },
  "params": {},
  "query": { "name": "takuma", "n": "3" },
  "body": {},
  "webhookUrl": "http://localhost:5678/webhook/hook-probe",
  "executionMode": "production"
}

後続のノードでは $json.body.〇〇 や $json.query.〇〇 で取り出します。

気をつける点が3つあります。

ヘッダー名は小文字になります。 X-Custom-Header を送っても、届くのは x-custom-header です。$json.headers['X-Custom-Header'] と書くと undefined になります。

executionMode でテストか本番かを判別できます。 Test URL 経由なら test、 Production URL 経由なら production でした。テストのときだけ通知を飛ばさない、 といった分岐に使えます。

params はパスに : を使ったときに入ります。 Path を order/:id のように 書いた場合です。普通のパスなら空のままです。

クエリは文字列、ボディは型が残る

これは実務で効きます。

?n=3 を送ると、query.n は "3"(文字列) になります。一方、 POST で {"count": 5} を送ると、body.count は 5(数値) のままです。

"query": { "n": "3" },          // 文字列
"body": { "count": 5 }          // 数値

クエリ文字列にはもともと型が無いので当然ではあるのですが、混ざると事故ります。 n8n の比較は型に厳しく、1 と '1' は別物として扱われます (Merge ノードの記事で実際に一致しなくなった例を書きました)。

クエリで受けた値を数値として使うなら、明示的に変換してください。

応答の返し方は4つある

Respond で、呼び出し元に何をいつ返すかを決めます。

Respondのドロップダウンを開いた状態。Immediately、When Last Node Finishes、Using 'Respond to Webhook' Node、Streaming の4つが説明文つきで並んでいる
Respond の選択肢は4つ。既定は Immediately です
表記 何が返るか 実測
Immediately(既定) 受け付けた時点で即返す {"message":"Workflow was started"} / 200
When Last Node Finishes 最後のノードの出力 1件目だけ / 200
Using ‘Respond to Webhook’ Node 別ノードで組み立てた内容 自由・ステータスも指定可
Streaming 対応ノードからリアルタイムに返す 未検証

When Last Node Finishes で返るのは1件目だけでした。 最後のノードが2件出力していても、応答に入ったのは1件目のみです。 全部返したいなら Respond to Webhook を使うことになります。

Respond to Webhook で応答を組み立てる

Respond を Using ‘Respond to Webhook’ Node にして、Respond to Webhook ノードを フローの中に置きます。

Respond to Webhookノードの設定画面。Respond WithがJSON、Response Bodyに式が入っており、OptionsのResponse Codeが201になっている
Response Code を 201 にした状態。既定は 200 です

Respond With で返す形を選びます。

Respond Withのドロップダウン。All Incoming Items、Binary File、First Incoming Item、JSON、JWT Token、No Data が説明文つきで並んでいる
All Incoming Items を選べば、全アイテムを返せます
  • All Incoming Items — 入力の全アイテムを返す
  • First Incoming Item — 1件目だけ(既定)
  • JSON — 自分で組み立てたJSONを返す
  • ほかに Binary File / JWT Token / No Data など

JSON を選び、Response Body に式を入れて、Options から Response Code を 201 にしたところ、そのとおり 201 が返りました。

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8

{"ok":true,"受け取った件数":2}

このノードには注意書きが出ています。

When using expressions, note that this node will only run for the first item in the input data

式を使う場合、このノードは1件目に対してしか動きません。 件数を数えるような処理は $input.all().length のように書く必要があります。

検証していないこと

  • Streaming は試していません。 対応ノードが必要なため、確認できていません
  • Authentication は None のままで検証しました。Basic / Header / JWT は未確認です
  • 外部からのアクセスは試していません。 すべて localhost からの呼び出しです。 インターネット経由で受ける場合の公開方法(トンネル、リバースプロキシ)には触れていません
  • params(Path に : を使う形)は実際に叩いていません。 空だったことを 確認しただけです
  • 日本語のボディを送る検証で一度文字化けしましたが、原因は私の送信側のツールが UTF-8 で送っていなかったことでした。UTF-8 で送り直したら正常に届いています。 n8n の挙動ではありません

Webhook は「設定は合っているのに動かない」が起きやすいノードですが、 原因はURLの種類・Publish の有無・メソッド・押したボタンのどれかに収まりました。

次に読む