An n8n webhook is a URL that starts a workflow when another service sends it an HTTP request. Build against the test URL, publish to switch on the production URL, and answer callers with the Webhook node itself or a Respond to Webhook node. For anything public, add authentication or verify the HMAC signature of the sender on the raw body before the workflow does real work.
An n8n webhook is a Webhook trigger node that gives your workflow its own URL: when a service sends an HTTP request to that URL, the workflow runs with the request's body, headers, query and path parameters as its input. You build with the test URL, publish the workflow to switch on the production URL, and choose whether n8n replies immediately, after the last node, or from a Respond to Webhook node.
Node behaviour checked October 2026 against the n8n docs for the Webhook node, its common issues page and the Respond to Webhook node. n8n renames menu labels between releases; recent versions say "publish" where older ones said "activate".
Webhooks are how you connect n8n to services that have no dedicated trigger node, and how you turn a workflow into a small API. They are also the part of an n8n setup most often left wide open: a production URL with no auth, no signature check, and a workflow that writes to your CRM for anyone who finds it. This guide covers the mechanics first, then the security layer most tutorials skip: a webhook-or-poll decision table, signature checks for Stripe, GitHub, Shopify and Slack, and the production URL setup for self-hosted instances. If you are still picking which automations to build, the n8n templates library is the hub for this series.
- 01Sender
Stripe, GitHub, a form, your app: sends an HTTP request to the production URL.
- 02Webhook node
Checks method, auth and IP allowlist; keeps the raw body when Raw Body is on.
- 03Verify
Recompute the HMAC over the raw body and compare it with the signature header.
- 04Reply fast
Respond to Webhook returns a 2xx before slow steps run.
- 05Do the work
Write to the CRM, call AI, notify the team.
- 06Catch failures
An error workflow alerts you when a run breaks.
Should you use an n8n webhook or polling?
A webhook waits for the sender to push an event. Polling asks the sender for changes on a timer, either through an app's own trigger node or a Schedule Trigger plus an HTTP Request node. Neither is better in general; the right one depends on what the sender supports and how costly a missed event is.
| Situation | Use | Why |
|---|---|---|
| The sender can push events | Webhook | Events arrive as they happen and nothing runs when nothing changes. |
| The sender has no webhooks or only a dedicated n8n trigger | Polling trigger or Schedule Trigger | Ask the API for changes on a timer and track the last item you saw. |
| You need a reply in the same request (a form, a chatbot, an internal API) | Webhook with Respond to Webhook | The caller waits for your answer, so keep the path short. |
| Missing one event is costly (payments, orders) | Webhook plus a scheduled reconciliation run | Webhooks can be delayed or retried; a daily check catches gaps. |
| Your n8n runs on a laptop or private network | Polling, or a tunnel for testing only | Outside services cannot reach localhost; the n8n docs suggest tunnel mode for local tests. |
| High volume bursts from one sender | Webhook that responds immediately, then queues work | Return a fast 2xx and let slower steps run after the reply. |
The last row trips up many self-hosters. A service on the internet cannot call http://localhost:5678. The n8n docs suggest running n8n in tunnel mode for local testing; for production, the instance needs a public HTTPS address, covered in the self-hosting section below.
How does the n8n webhook trigger work?
Add a Webhook node as the first node of a workflow. Its main settings, as documented by n8n (checked October 2026):
- HTTP Method. DELETE, GET, HEAD, PATCH, POST or PUT. The node accepts one method by default; turn on Allow Multiple HTTP Methods in the node's Settings to accept several, and the node then gets one output per method.
- Path. A random path by default, to avoid clashes. You can set your own, including route parameters such as
/:variableor/path/:variable, which is handy when you prototype an API and want stable URLs. - Authentication. Basic auth, Header auth, JWT auth or None, set through Webhook credentials.
- Respond. Immediately, When Last Node Finishes, Using Respond to Webhook Node, or Streaming response for nodes that support streaming, such as the AI agent node.
- Options. Allowed Origins (CORS), Binary Property, Ignore Bots, IP(s) Allowlist, Raw Body, Response Headers, Response Content-Type and Only Run If, an expression that must return true for the workflow to run.
Two limits to know. The maximum payload is 16MB, adjustable on self-hosted instances with N8N_PAYLOAD_SIZE_MAX. And n8n allows only one webhook per path and HTTP method combination, so two published workflows cannot both own POST /leads.
What is my n8n webhook URL, test or production?
Every Webhook node has two URLs, shown at the top of the node panel with a toggle between them.
- Path starts with /webhook-test/ by default
- Listens after Listen for test event or Execute workflow
- Stays active for 120 seconds
- Incoming data shows in the editor
- Use it while building and debugging
- Path starts with /webhook/ by default
- Registered when you publish the workflow
- Live until you unpublish
- Data shows only in the Executions tab
- Give this one to the sending service
Source: n8n docs, checked October 2026
The classic mistake is pasting the test URL into Stripe or a form tool, seeing it work once while the editor was listening, and then wondering why nothing runs the next day. The test URL stops listening after 120 seconds. Once the workflow is ready, copy the production URL into the sender and publish. The default path prefixes come from the N8N_ENDPOINT_WEBHOOK and N8N_ENDPOINT_WEBHOOK_TEST variables in the endpoints reference; most instances never change them.
To test the production path without a real event, call it yourself with curl or an HTTP Request node from another workflow, then open the Executions tab to see what arrived. Our n8n API integration guide covers the HTTP Request side if you are wiring n8n to call other services too.
When do you need the n8n Respond to Webhook node?
The Webhook node can answer on its own: Immediately sends the status code and "Workflow got started", and When Last Node Finishes returns the last node's output. You need the Respond to Webhook node when the reply is not simply "whatever came out last", for example when you want to answer the caller first and keep working afterwards.
Set Respond on the Webhook node to Using Respond to Webhook Node, then place the Respond to Webhook node where the answer should go out. It can respond with all incoming items, the first incoming item, a JSON body you define, text, a binary file, a JWT, a redirect or no data, and it takes a custom response code and headers. Behaviours worth knowing, from the n8n docs (checked October 2026):
- It runs once, using the first incoming item. To return many items, choose All Incoming Items or aggregate them first.
- If the workflow finishes without reaching it, the caller gets a standard 200 message.
- If the workflow errors before it runs, the caller gets a 500.
- A second Respond to Webhook node after the first is ignored.
- Since n8n 1.103.0, HTML responses are wrapped in a sandboxed iframe, so scripts that touch the top window or local storage fail and relative URLs do not work.
Authenticating an n8n webhook you control
When you control the sender (your own app, a form backend, another n8n instance), use the Webhook node's built-in authentication. Header auth is the simplest: create a Webhook credential with a header name and a long random value, and have the sender include that header. Basic auth works the same way with a username and password. JWT auth validates a signed token using a passphrase or PEM key from a JWT credential.
On top of that, three node options narrow who can trigger a run:
- IP(s) Allowlist. Requests from addresses outside the list get a 403. Behind a reverse proxy, set
N8N_PROXY_HOPSto the number of proxies, or n8n sees the proxy's address instead of the caller's. - Only Run If. An expression against
{ body, headers, params, query }. Non-matching requests get a 200 and create no execution. If the expression fails to evaluate, n8n logs a warning and lets the request through, so do not treat it as your only gate. - Ignore Bots. Drops link previewers and crawlers, which matters when a URL gets pasted into chat tools.
How do you verify webhook signatures in n8n?
Services you do not control usually cannot send a custom auth header. Instead they sign each request: they compute an HMAC-SHA256 of the raw request body with a secret you share, and put the result in a header. Your job is to compute the same HMAC and compare. The detail that breaks most attempts is the word raw: Stripe, Shopify and Slack all say verification needs the exact bytes as sent, and re-serialised JSON will not match.
In n8n, open the Webhook node's options and turn on Raw Body. The n8n docs on Extract From File note that Raw Body makes the Webhook node output the request as binary data, in a binary field named data by default. From there, n8n's built-in hashing and signing node (search the nodes panel for "Hmac") has an Hmac action that can hash a binary file or a text value with SHA256 and output HEX or BASE64, taking the secret from a stored credential rather than from the node itself. Here is what each of four common senders expects, from their own docs:
| Sender | Signature header | What is signed | Encoding and compare |
|---|---|---|---|
| Stripe | Stripe-Signature (t=timestamp, v1=signature) | timestamp + "." + raw body | HEX, compare with each v1 value; reject old timestamps |
| GitHub | X-Hub-Signature-256 | raw body | HEX, header value is "sha256=" + digest |
| Shopify | X-Shopify-Hmac-SHA256 | raw body | BASE64, compare with the header as sent |
| Slack | X-Slack-Signature with X-Slack-Request-Timestamp | "v0:" + timestamp + ":" + raw body | HEX, header value is "v0=" + digest; reject requests older than five minutes |
Sources, checked October 2026: Stripe webhooks (verify signatures manually), GitHub, validating webhook deliveries, Shopify, verify deliveries, Slack, verifying requests.
Pattern A: the body alone is signed (GitHub, Shopify)
- 1Webhook node
Method POST, Respond: Using Respond to Webhook Node, Raw Body on.
- 2Hmac action
Binary File on, binary property data, type SHA256, encoding HEX for GitHub or BASE64 for Shopify. Store the secret in the credential.
- 3If node
GitHub: "sha256=" + computed value equals the x-hub-signature-256 header. Shopify: computed value equals x-shopify-hmac-sha256.
- 4False branch
Respond to Webhook with 401 and stop.
- 5True branch
Respond to Webhook with 200, then continue to the real work.
Header names reach n8n in lower case, so reference them that way, for example:
{{ 'sha256=' + $json.hmac === $('Webhook').item.json.headers['x-hub-signature-256'] }}Replace hmac with whatever Property Name you set on the Hmac action. Shopify adds one wrinkle: when you rotate the app's client secret, Shopify says it can take up to an hour before digests use the new secret, so keep the old secret available during a rotation.
Pattern B: a timestamp is signed with the body (Stripe, Slack)
Stripe signs timestamp.body and Slack signs v0:timestamp:body. The binary option cannot prepend the timestamp, so turn the raw body into text first: add an Extract From File node with the Extract From Text File operation, input binary field data, and a destination output field such as raw. Then point the Hmac action at a text value built with an expression:
// Stripe: t comes from the Stripe-Signature header (t=...,v1=...)
{{ $('Webhook').item.json.headers['stripe-signature'].split(',').find(p => p.startsWith('t=')).slice(2) + '.' + $json.raw }}
// Slack
{{ 'v0:' + $('Webhook').item.json.headers['x-slack-request-timestamp'] + ':' + $json.raw }}Compare with HEX output. For Stripe, the header can hold more than one v1 value while you roll a secret, so accept a match with any of them and ignore other schemes. For both senders, also reject old timestamps: Slack's example rejects requests more than five minutes from local time, and Stripe's libraries default to a five-minute tolerance. A second If node comparing the timestamp with $now covers it.
The production URL pattern for self-hosted n8n
On n8n Cloud the URL is public by default. Self-hosted, n8n usually sits behind a reverse proxy such as Caddy, Nginx or Traefik, and it cannot work out its own public address. The n8n docs on webhook URLs behind a reverse proxy (checked October 2026) list three settings:
N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1
# and on the last proxy, forward:
# X-Forwarded-For, X-Forwarded-Host, X-Forwarded-ProtoN8N_WEBHOOK_URL replaces WEBHOOK_URL, which still works but is deprecated from n8n 2.35.0 and logs a warning. Without it, the editor shows a localhost URL, and trigger nodes that register webhooks with outside services register the wrong address. Our n8n Docker Compose setup guide shows where these variables go in a compose file, and the self-hosting guide covers the proxy and TLS side.
A few habits that keep self-hosted webhooks tidy: use a dedicated subdomain for n8n, set a readable custom path per integration (/stripe-events, /github-push) so logs make sense, and keep one webhook per sender so a leaked secret only affects one workflow.
Common n8n webhook mistakes
- Test URL in production. It stops listening after 120 seconds. Use the production URL and publish.
- Workflow not published. The production URL only exists while the workflow is published.
- Wrong method. The sender posts, the node expects GET. Match them or allow multiple methods.
- Hashing parsed JSON. Signatures are computed over the raw bytes. Turn on Raw Body.
- Doing slow work before replying. Senders time out and retry, and you process the same event twice. Reply first, and de-duplicate on the event ID the sender provides.
- No failure alerting. A broken production webhook fails quietly. Attach an error workflow; our n8n error handling guide shows the setup.
If you want to go further than single workflows, our AI SaaS Builder program covers building webhook-driven backends, auth and payments into a product you can ship.
- Copy the production URL, not the test URL
- Publish the workflow and confirm it shows as published
- Set a readable custom path and the exact HTTP method the sender uses
- Add Header, Basic or JWT auth, or verify the HMAC signature of the sender on the raw body
- Add the IP allowlist if the sender publishes fixed addresses
- Reply with Respond to Webhook before slow steps
- De-duplicate on the event ID the sender provides
- Self-hosted: set N8N_WEBHOOK_URL and N8N_PROXY_HOPS
- Attach an error workflow so failures reach you
n8n webhook FAQ
What is the difference between the n8n test URL and production URL?
The test URL only listens after you select Listen for test event or execute the workflow, stays active for 120 seconds, and shows the incoming data in the editor. The production URL is registered when you publish the workflow, stays live until you unpublish it, and does not show data in the editor; you inspect those runs in the Executions tab. Checked October 2026 against the n8n docs.
Why does my n8n webhook return 404?
The usual causes are calling the test URL when nothing is listening, calling the production URL of a workflow that is not published, or using the wrong HTTP method. The Webhook node accepts one method by default, so a GET to a POST webhook fails. Also check that the path is not used by another published webhook, because n8n allows only one webhook per path and method.
How do I secure an n8n webhook?
Layer the controls. Use Header auth, Basic auth or JWT auth on the Webhook node for senders you control. For services that sign their requests, turn on Raw Body and check the HMAC signature before doing anything else. Add the IP allowlist where the sender publishes fixed addresses, and use Only Run If to drop requests that are not the event you expect.
How do I return data from an n8n webhook?
Set Respond in the Webhook node to When Last Node Finishes to return the output of the last node, or to Using Respond to Webhook Node and place a Respond to Webhook node where you want the reply sent. That node can answer with JSON, text, a binary file, a redirect, a JWT or all incoming items, with a custom status code and headers.
What is the maximum payload size for an n8n webhook?
The n8n docs list a 16MB maximum payload for the Webhook node. On a self-hosted instance you can change it with the N8N_PAYLOAD_SIZE_MAX environment variable. For larger files, have the sender upload to storage and send n8n a link instead of the file itself. Checked October 2026.
Why does my self-hosted n8n show localhost in the webhook URL?
n8n builds the URL it displays from its own configuration, so behind a reverse proxy it does not know your public domain. Set N8N_WEBHOOK_URL to your public HTTPS address, set N8N_PROXY_HOPS to 1, and make the proxy forward the X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto headers. WEBHOOK_URL still works but is deprecated from n8n 2.35.0.
Building on webhooks? Turn workflows into a product.
AI SaaS Builder, included in All Access, covers backends, auth, payments and automation, with the other three programs, live coaching and the private community in one subscription.
Start with the free Creator Starter Kit
Templates and checklists for your first automations, free.