You built the workflow, it ran beautifully when you clicked "Listen for test event," you pasted the URL into Stripe, and now nothing happens. No executions. No errors. The provider says the delivery failed, or worse, says it succeeded and you still have nothing.
This is a small set of causes with a very long tail of confusion, mostly because n8n's failure messages are quiet and the most common cause is a URL that looks correct. Here they are in the order they're actually responsible.
TL;DR
- Test URL vs production URL is the answer most of the time. Test only listens while you're looking at it
- The production URL only exists once the workflow is activated
- Response mode defaults to replying immediately, which is fine for most providers and fatal if you need to return a challenge
- Raw Body must be on for any signature verification, or the HMAC will never match
- A failed execution loses the event. n8n does not retry incoming webhooks
- Self-hosted behind a reverse proxy adds a body-size and timeout layer people forget about
1. You're using the test URL (this is the one)
The n8n Webhook node shows two URLs, and they behave completely differently:
Test: https://your-instance/webhook-test/abc-123
Production: https://your-instance/webhook/abc-123
The test URL is live only while you have clicked "Listen for test event" and are watching the canvas. It registers one execution, shows you the data, and stops. The moment you navigate away it returns 404 to everything.
That is exactly the right design for building, and exactly wrong for the URL you paste into Stripe. And because it worked perfectly during development, the mental model that forms is "the webhook works," which makes the real cause hard to see.
Go look at the URL registered with your provider. If it contains webhook-test, that's your bug, and you can stop reading.
2. The workflow isn't activated
The production URL is registered when the workflow is activated (published). An inactive workflow's production URL returns 404, because as far as the server is concerned that route does not exist.
The trap: saving is not activating. You can edit, save, and close a workflow all day and the production route never updates. Related failures in the same family:
- Deactivating to make a change, then forgetting to reactivate. Every event during that window is gone
- Duplicating a workflow to test a change. The copy is inactive, and the original is still the one receiving traffic
- Changing the webhook node's path, which changes the URL. The provider is still pointed at the old one
Check the toggle in the editor, and check that the provider's URL matches the current path exactly.
3. Response mode is wrong for what the provider expects
The Webhook node's Respond setting controls when and what n8n replies:
| Mode | Behaviour |
|---|---|
| Immediately | Returns 200 with "Workflow got started" as soon as the event lands |
| When Last Node Finishes | Holds the connection until the workflow completes, returns the last node's output |
| Using 'Respond to Webhook' node | You control status and body explicitly |
Immediately is the right default and you should stay on it. It returns fast, which keeps you inside every provider's timeout regardless of how long the workflow takes.
Two cases where it's wrong:
Verification handshakes. Some providers require you to echo a challenge value on subscription, such as a token or a hash of the body. "Immediately" replies with a fixed message and the subscription is rejected. You need the Respond to Webhook node to return the exact expected value.
When Last Node Finishes, with a slow workflow. Now your response time is your entire workflow's runtime. Three API calls and a Google Sheets write is easily 8 seconds, and Shopify's budget is five. The provider records a timeout, retries, and your workflow runs a second time, from the top. This is the mode that silently creates duplicate rows.
If you're on "When Last Node Finishes" and don't specifically need the output in the response, switch to Immediately.
4. Signature verification fails because Raw Body is off
If you're verifying a Stripe, Shopify, or GitHub signature inside n8n, the Webhook node's Raw Body option is mandatory.
Providers HMAC the exact bytes they sent. With Raw Body off, n8n parses the JSON, and any code node that re-serializes it produces different bytes: different key order, different whitespace. The data is identical but the signature is not. You'll be staring at a payload that's visibly correct while the verification fails, which is a genuinely maddening hour.
Turn on Raw Body, then compute the HMAC over the raw string before parsing anything:
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) };
Use timingSafeEqual, not ===. And note it throws on length mismatch, so guard that if you're feeding it untrusted input. More on the per-provider formats in Verifying Stripe, GitHub, and Shopify Webhook Signatures.
5. A failed execution loses the event permanently
This is the one that costs real money, and it is not a bug. It is just how the model works.
With Respond set to Immediately, n8n has already returned 200 before your workflow runs. The provider considers the event delivered and will never send it again. If node 4 then fails on an expired token, a rate limit, or a malformed field, that execution is marked failed and the event exists nowhere except that failed execution record.
There is no automatic retry of the incoming webhook. There is no queue in front of it. The provider won't resend, because from its perspective everything went fine.
What to do about it:
- Set an Error Workflow in workflow settings. At minimum it should notify you. Failing silently is the default and it is not a good default
- Persist the raw payload as node 1. Before any transformation, write the body to Postgres, Airtable, a Google Sheet, anything durable. Then a failed run is replayable from your own store rather than lost
- Use
continueOnFaildeliberately on nodes where partial success is acceptable, and not on nodes where it silently swallows the failure you needed to see - Check retention. Self-hosted n8n prunes execution data on a schedule. Once pruned, even the failed execution's payload is gone
6. Self-hosted: the reverse proxy is in the way
If you're self-hosting behind nginx, Traefik, or Caddy, a few layers can eat requests before n8n sees them:
- Body size limits. nginx defaults
client_max_body_sizeto 1MB and returns413on anything larger. A Shopify bulk payload or a base64 attachment will exceed that - Proxy timeouts. If
proxy_read_timeoutis shorter than your workflow, the proxy closes the connection and the provider records a failure even though n8n completed WEBHOOK_URLnot set. n8n generates the URLs it displays from this. Behind a proxy it will show internal addresses that work from your laptop and not from the internet- Queue mode workers. In queue mode the webhook must reach the main instance. Routing it to a worker gets a 404
The fast diagnostic: curl -X POST your production URL from outside your network. A 404 means routing or activation. A 413 means body size. A hang means timeout. A 200 with nothing in Executions means you hit the test URL.
7. It arrived, but a filter dropped it
Less common, worth ruling out: the execution exists but appears to have done nothing. Check the HTTP Method on the node, which defaults to GET, and a provider POSTing to a GET-only webhook gets a 404 that looks identical to a routing problem. Then check any IF or Switch node's conditions against the real payload shape, since providers nest differently than the test payload you built against.
Where AnyHook fits
Cause 5 is the structural one. n8n executes workflows. It does not store the event first, and the moment it returns 200, the provider's copy is the only other one that existed.
Putting AnyHook in front changes what "delivered" means:
- Point the provider at
in.anyhook.net/you/appand AnyHook at your n8n production URL. Two URL changes, no workflow rewrite - The event is persisted before n8n is called. A failed execution is replayable from the log, not gone
- Retries with exponential backoff against your n8n instance, so a rate-limited node or a restart during an upgrade recovers on its own
- Signature verification at the edge. Configure the provider's secret once and get a single
AnyHook-Signatureheader, so the Raw Body dance in cause 4 becomes one HMAC check you write once for every provider - Failure alerts at 1, 5, and 20 consecutive failures, with auto-pause so a broken workflow doesn't retry-storm
The version that matters in practice: an n8n upgrade that takes the instance down for ten minutes currently costs you every event in those ten minutes. With a relay in front it costs you a replay click.
Takeaway
Check the URL first. webhook-test versus webhook is the answer far more often than anything else, and activation is a close second. Once it's receiving, the thing to fix before you forget is persistence: write the raw payload as your first node, and set an error workflow. n8n will not resend what it dropped, and neither will the provider.