Automation
N8n Webhook Not Triggering in Production Mode: 7 Causes and Fixes
Your n8n webhook not triggering in production mode is almost always one of seven things. Here's how webhook registration actually works, what each HTTP status code tells you, and how to isolate the cause in under five minutes.

If your n8n webhook works when you hit “Test workflow” but goes silent in production, the cause is almost certainly one of three things, in this order of frequency: the workflow toggle in the top-right is off, you’re calling /webhook-test/ instead of /webhook/, or the workflow is firing and you’re staring at a canvas that will never show it. That last one catches more people than the other two combined — n8n does not render production executions on the editor canvas. Ever. The run happened, the nodes went green, and none of it appeared in front of you because the canvas only draws manual and test runs. The Executions tab is the only place production runs show up.
So the first move is not to change anything. It’s to send one request and read the response body carefully. Run curl -i -X POST https://your-n8n-host/webhook/your-path -H 'Content-Type: application/json' -d '{"ping":true}' from a machine outside your network. The status code and the JSON body narrow this down to a single cause in one shot, because n8n’s webhook layer returns different, quite specific errors depending on where in the pipeline the request died. Below is what each one means mechanically, followed by the seven causes in the order they actually show up in real installs.
Reading the response before you change anything
The request passes through four layers before your first node runs: your reverse proxy, n8n’s Express router, the webhook registration lookup, and then the node’s own auth and payload checks. Each layer fails differently.
| What you get back | Where it died | What that implies |
|---|---|---|
404 with JSON containing “not registered” and a hint about activating the workflow |
n8n’s webhook lookup | Request reached n8n. Path is unregistered for that method. |
404 from nginx/Traefik/Caddy (HTML page, no JSON) |
Reverse proxy | Request never reached n8n at all. |
403 |
Webhook node auth | Node has Basic/Header auth configured and credentials didn’t match. |
413 |
Body parser | Payload exceeded the configured max size. |
200 with {"message":"Workflow was started"} |
n8n, successfully | It fired. Your problem is downstream or in visibility. |
500 |
Inside the workflow | It fired and a node threw. Check Executions. |
502 / 504 |
Reverse proxy timeout | Workflow is running longer than the proxy will wait. |
| Connection refused / timeout | Network | DNS, firewall, or n8n isn’t listening on that host. |
The distinction between a JSON 404 and an HTML 404 is the single highest-value signal here. A JSON 404 that mentions webhook registration means your networking is fine and the problem is inside n8n. An HTML 404 means you have a proxy problem and nothing you do inside the editor will change anything.
Cause 1: the workflow is saved but not active
Test URLs and production URLs are served by two different registries. When you click “Test workflow” or “Listen for test event”, n8n registers the path in an in-memory, single-use listener under the /webhook-test/ prefix. It answers one request, then unregisters itself. Saving the workflow does nothing to the production registry. Only flipping the Active toggle writes a row that makes /webhook/<path> resolve.
This is why “it worked five minutes ago” is so common: the test listener was alive during that window. The response you get when the production path is unregistered is a JSON 404 whose hint text explicitly says the workflow must be active for a production URL to run, and — this is the part people skim past — that production URL calls aren’t shown on the canvas.
One deployment-specific wrinkle: workflows imported via the CLI, restored from a backup, or synced through source control arrive inactive by default. A workflow that was active in staging is not active in production just because the JSON says so.
Cause 2: it is triggering, and execution saving is hiding it
Assume the 200 response came back. You check Executions and the list is empty. That doesn’t mean nothing ran — it can mean nothing was persisted.
Each workflow has its own settings panel (the gear icon → Settings) with separate switches for saving successful production executions and failed production executions. Those default to the instance-wide values set by EXECUTIONS_DATA_SAVE_ON_SUCCESS and EXECUTIONS_DATA_SAVE_ON_ERROR. Plenty of self-hosted installs set the success value to none to keep the database from ballooning, then forget. The result is a workflow that runs perfectly and leaves no trace, which reads exactly like a webhook that never fired.
There’s also a pruning layer: EXECUTIONS_DATA_PRUNE with EXECUTIONS_DATA_MAX_AGE (in hours) will delete older runs on a schedule. On a busy instance with aggressive pruning, a run from this morning can genuinely be gone by afternoon.
The mechanical test that cuts through all of it: temporarily set the workflow’s own “Save successful production executions” to save, fire one request, and look again. If the run now appears, nothing was ever broken about the webhook.
Cause 3: method, path, and the trailing slash
The registration key is the pair (method, path), not the path alone. A Webhook node configured for POST does not answer GET on the same path — it returns the same “not registered” 404 you’d get from an inactive workflow, which sends people chasing the wrong cause for an hour. If a service needs to hit the same endpoint with more than one verb, the node’s HTTP Method field accepts multiple methods in recent versions; older versions need one node per method, each with its own path.
Other things that break the key match:
- Trailing slashes.
/webhook/ordersand/webhook/orders/are not guaranteed to resolve identically once a proxy with its own rewrite rules sits in front. - Path parameters. A path written as
orders/:idregisters a pattern, not a literal. Calling/webhook/orders/:idverbatim will not match; calling/webhook/orders/123will. - The webhook ID form. n8n also exposes
/webhook/<uuid>/<path>for some node configurations. If you copied the URL from an older version of the workflow and then changed the path, the copied URL is stale. - Case. Paths are matched as written.
OrdersInandordersinare different registrations.
Copy the production URL straight out of the node panel each time rather than reconstructing it by hand. The panel renders the URL from the same values the registry uses.
Cause 4: n8n is advertising a URL that doesn’t reach it
Behind a reverse proxy or tunnel, n8n has no way to know its own public address. It builds the URL shown in the node panel from its configuration, and if that configuration is unset it happily displays http://localhost:5678/webhook/... — which is correct from inside the container and useless to Stripe.
The variables that control this are WEBHOOK_URL (the full public base URL, the one that matters when a proxy is involved) and, for simpler setups, N8N_HOST, N8N_PROTOCOL and N8N_PORT. Setting WEBHOOK_URL changes what n8n displays and sends to external services; it does not change what your proxy forwards. Both sides have to agree.
Two more knobs worth knowing about, because they silently invalidate every URL you’ve written down:
N8N_ENDPOINT_WEBHOOKchanges thewebhookpath segment itself (defaultwebhook), andN8N_ENDPOINT_WEBHOOK_TESTdoes the same forwebhook-test. If someone hardened the install by renaming these, the canonical URL is no longer/webhook/....N8N_PATH/ running n8n under a subpath means the proxy has to strip or preserve the prefix consistently.
If the proxy is stripping a prefix it shouldn’t, the symptom is a JSON 404 from n8n mentioning a path you never configured. That error message tells you exactly what n8n received — read the path in it and compare with what you sent.
Cause 5: the request is rejected before the workflow starts
Three checks run inside the Webhook node before any downstream node executes.
Authentication. If the node’s Authentication field is set to Basic Auth or Header Auth, requests without matching credentials get a 403. This one bites when a workflow is duplicated: the credential reference copies over, but the sending system’s config doesn’t.
Payload size. N8N_PAYLOAD_SIZE_MAX caps the accepted body, expressed in megabytes and set to 16 by default in recent versions — worth confirming against your own version’s docs rather than assuming. A webhook that works for small test payloads and dies on the real one is the classic shape of this. The failure is a 413 and it never reaches your nodes. Enabling the node’s “Raw Body” option changes how the body is parsed, not how large it may be.
CORS. Browsers send an OPTIONS preflight before a cross-origin POST. Unless the node’s “Allowed Origins (CORS)” option is set, that preflight isn’t answered the way the browser needs and the actual POST is never sent. In the browser this looks like a CORS error, in n8n it looks like total silence, and in your server logs you’ll see an OPTIONS request and no POST.
Cause 6: response mode mismatch makes the sender give up
The Webhook node’s Respond field has three modes and they change the contract with the caller:
- Immediately — returns
{"message":"Workflow was started"}right away and runs the rest asynchronously. - When Last Node Finishes — holds the connection open for the entire execution.
- Using ‘Respond to Webhook’ Node — holds the connection until that node executes.
Two failure modes come out of this. First, if the mode is set to “Using ‘Respond to Webhook’ Node” and execution takes a branch where that node is never reached, the caller hangs until something times out. Second, most webhook providers have short delivery timeouts — GitHub, Stripe and similar services expect a response in single-digit seconds and mark the delivery failed otherwise, then retry. A workflow that takes 30 seconds under “When Last Node Finishes” will look, from the provider’s delivery log, like a dead endpoint. And the retries mean your workflow runs three or four times for one event, which is a different bug entirely.
Your reverse proxy has its own opinion here: nginx’s proxy_read_timeout defaults to 60 seconds, and exceeding it produces a 504 that the workflow never learns about. Meanwhile n8n’s own execution timeout is governed by EXECUTIONS_TIMEOUT, which is disabled by default in standard installs.
Every provider that sends webhooks keeps a delivery log with the response code it received. That log is ground truth about what happened on the wire, and it’s often the fastest way to end an argument about whether the request was ever sent.
Cause 7: registration conflicts and multi-instance deployments
Production webhook registrations are persisted — on self-hosted installs there’s a webhook_entity table in the n8n database mapping method and path to workflow. Two consequences follow.
Path collisions block activation. If another workflow — including an archived or forgotten one — already owns the same method and path, activation fails with an error naming the conflicting workflow ID. If that error appeared as a toast while you were looking elsewhere, the toggle simply didn’t stay on. Reloading the workflow list and confirming the workflow reads as Active is the check.
Scaling changes who answers. In queue mode, work is distributed across worker processes, and some deployments run dedicated webhook processes via the n8n webhook command. If your configuration disables production webhook handling on the main process (there’s an environment variable for exactly this — check your version’s docs for the current name, as it has changed across releases) but no webhook process is actually running or routed to, test webhooks keep working on the main instance while production webhooks 404. That asymmetry is the fingerprint of this cause.
On restart, n8n reloads active workflows from the database and re-registers their webhooks. If the process crashed mid-activation or the database is out of sync with reality, toggling a workflow off, saving, and toggling it back on rewrites the registration cleanly.
The bonus gotcha: pinned data
Pinned data on a Webhook node applies to manual executions only. In production the node uses the real incoming request. So a workflow that behaves perfectly in test and produces nonsense in production — empty fields, missing IDs, downstream nodes erroring on undefined — hasn’t failed to trigger. It triggered with data that doesn’t look like the pinned sample. Comparing the input JSON of a production execution against the pinned object usually makes the difference obvious in seconds.
A five-minute isolation sequence
Working outside-in, each step eliminates a whole layer:
- From outside your network,
curl -ithe production URL with the exact method the sender uses. Note whether the 404 is JSON (n8n answered) or HTML (proxy answered). - Confirm the toggle reads Active in the workflow list, not just in the editor tab you have open.
- Copy the production URL fresh from the node panel and diff it character by character against what the sender is configured with.
- Open Executions and filter to that workflow — remembering the canvas will never show a production run.
- Force execution saving on for that workflow, then send one more request.
- Read the sender’s delivery log for the status code it actually received.
- Check the n8n logs. Raising
N8N_LOG_LEVELtodebugand watchingdocker logs -fwhile you fire a request tells you whether the process saw anything at all.
Where things land after step one is usually decisive: an HTML 404 means the rest of your afternoon belongs to the proxy config, and a JSON 404 means it belongs to the workflow. Which of the seven causes applies depends on your deployment shape — a single Docker container behind Caddy fails differently from a queue-mode cluster behind an ALB, and the environment variable names above have moved between major versions, so the docs for your exact version are worth checking before changing anything.
Webhooks are the piece of an automation stack most likely to look broken while working exactly as designed, which is a good argument for putting effort into observability before adding more workflows on top — the same reasoning behind picking tools that surface what they’re doing, something I got into in 5 AI tools that quietly replaced half my workflow. The n8n webhook layer is not mysterious; it’s just a registry keyed on method and path, with a persistence step and a proxy in front. Once you can say which of those four layers swallowed the request, the fix is rarely more than one toggle away.