Guide to n8n Webhooks: Features and Best Practices
n8n workflows often end by telling another system that something happened: a lead was captured, an order was synced, or a long-running workflow finished or failed. In n8n, that call is made with the HTTP Request node. If your service is on the receiving end of those calls, you are running a webhook endpoint for n8n, even though n8n has no webhook product of its own on the sending side.
This guide covers how the HTTP Request node sends requests, how to authenticate them when n8n does not sign anything, the node settings that affect delivery, and the best practices for an endpoint that n8n workflows call. It is about requests that leave n8n. The Webhook trigger node, which receives requests into a workflow, is a different topic.
What are n8n webhooks?
An n8n webhook, in this sense, is a request built by the workflow author. The HTTP Request node lets the author set the URL, the method, query parameters, headers and the body. n8n adds no event type, no event ID and no envelope, so the payload is whatever the workflow sends.
The method is the author's choice of DELETE, GET, HEAD, OPTIONS, PATCH, POST or PUT. The body content type is also a choice: Form URLencoded, Form-Data, JSON, an n8n Binary File, or Raw content with a content type you name.
n8n does not sign these requests. Authentication comes from an n8n credential attached to the node, which adds a header with a value only you and the receiver know.
n8n HTTP Request node features
| Feature | Details |
|---|---|
| Sender | The HTTP Request node in any workflow, on n8n Cloud or self-hosted |
| Method | DELETE, GET, HEAD, OPTIONS, PATCH, POST or PUT |
| Body | Form URLencoded, Form-Data, JSON, n8n Binary File or Raw |
| Event name | None from n8n. Add your own field, such as event |
| Event ID | None from n8n. Add your own field, such as id |
| Signature | None |
| Authentication | Generic credentials: Header auth, Bearer auth, Basic auth, and others |
| Header names | Lowercased by default (the Lowercase Headers option) |
| Timeout | Optional. How long the node waits for the receiver's response headers |
| Batching | Optional. Items per batch and an interval between batches |
| Success | By default only a 2xx response. Never Error treats any response as success |
| Source IPs | Not published. A self-hosted instance sends from wherever it runs |
Event payloads
Because n8n defines no events, agree a payload shape with the people who build the workflows. A small envelope with a name, an ID and the data makes the receiver much simpler:
{
"event": "order.created",
"id": "order-1042-created",
"data": {
"order_id": "1042",
"total": 129.5,
"currency": "GBP"
}
}
Build the id in the workflow from something that is unique in your data, such as the record's own ID and the kind of change, so a repeated request carries the same key. Your receiver then routes on event and deduplicates on id.
Setting up an n8n workflow to send webhooks
- Generate a long random secret, for example with
openssl rand -hex 32. - In n8n, add an HTTP Request node where the workflow should notify your service.
- Set Method to
POSTand URL to your endpoint. - Under Authentication, choose Generic Credential Type and then one of:
- Header Auth, with a header Name such as
X-Webhook-Secretand your secret as the Value - Bearer Auth, with your secret as the Bearer Token. n8n sends it as
Authorization: Bearer <token> - Basic Auth, with a username and password
- Header Auth, with a header Name such as
- Turn on Send Body, choose JSON, and enter your payload.
- Run the node once and check the response your endpoint returned.
You can also add the header with Send Headers, but a credential keeps the secret in one place that every node can reuse.
Securing n8n webhooks
With no signature, verification means checking the header your credential adds. Compare the whole value with the one you stored, in constant time, and fail closed if your secret is not configured.
const crypto = require('crypto');
const express = require('express');
// The header your n8n credential sends. Node lowercases incoming header names.
const SECRET_HEADER = (process.env.N8N_WEBHOOK_HEADER || 'x-webhook-secret').toLowerCase();
function verifyN8nHeader(headers, expected) {
const received = headers[SECRET_HEADER];
if (typeof received !== 'string' || !expected) return false;
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/n8n', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.N8N_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyN8nHeader(req.headers, secret)) {
return res.sendStatus(401);
}
const payload = JSON.parse(req.body.toString('utf8'));
// Deduplicate on payload.id, route on payload.event, then process async.
res.sendStatus(200);
});
The same check in Python:
import hmac
def verify_n8n_header(headers, expected: str, header_name: str = "x-webhook-secret") -> bool:
received = headers.get(header_name)
if not received or not expected:
return False
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
For Bearer auth, set the header to authorization and store the full value, Bearer <token>. For Basic auth, do the same with Basic followed by the base64 of username:password.
Other n8n credentials don't fit this model. Digest auth is a challenge your server has to run, OAuth1 and OAuth2 tokens come from your own authorisation server, and Query auth or Custom auth values in the query string or body are not headers. If you want a header check, use Header, Bearer or Basic auth.
A static header proves the caller knows your secret. It does not prove the body is unchanged, and the header is identical on every request, so a captured request can be sent again.
Make n8n webhooks production-ready. Hookdeck Event Gateway checks your n8n credential header before anything reaches your code, queues every request durably, and lets you replay what failed.
n8n webhook limitations and pain points
No signature, only a static header
The Problem: An endpoint that a workflow calls without a credential accepts anything posted to it. With a credential, anyone who captures one request can send more.
Why It Happens: The HTTP Request node signs nothing. Its credentials add a fixed header, and that header is the same on every request.
Workarounds:
- Attach a Header, Bearer or Basic auth credential to every HTTP Request node that calls your service.
- Serve the endpoint over HTTPS and never log the header value.
- To rotate, accept both the old and new values for a short time, update the credential in n8n, then remove the old value.
How Hookdeck Can Help: Hookdeck's n8n source type checks a header for you. Enter the header name and the exact value, including any Bearer or Basic prefix, and Hookdeck rejects requests without it before they reach your service.
No event ID or event name unless you add one
The Problem: Your receiver can't tell one kind of request from another, or a repeated request from a new one.
Why It Happens: n8n sends only what the author puts in the node. Re-running a failed execution, or a workflow that processes the same item twice, sends the same data again with nothing to mark it as a repeat.
Workarounds:
- Put an
eventfield and anidfield in every payload, and document the shape for everyone who builds workflows. - Store each
idyou process and skip repeats.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated requests based on the fields you choose, such as the body's id, and filters can route on event.
The method and format are not fixed
The Problem: A handler that expects a JSON POST breaks when someone builds a workflow with PUT or a form body.
Why It Happens: Every node is configured by hand, and n8n offers seven methods and five body types.
Workarounds:
- Agree on
POSTwith a JSON body for every workflow that calls your service. - Return a clear
4xxfor anything else so the author sees the failure in n8n.
How Hookdeck Can Help: Hookdeck's n8n source starts with POST as its allowed method. If a workflow needs another method, add it to the source.
Delivery depends on how the node is configured
The Problem: A slow response or an outage on your side ends with lost data, or with a workflow that reports success when nothing arrived.
Why It Happens: n8n has no delivery policy of its own for these requests. The Timeout option is how long the node waits for your response headers. By default the node succeeds only on a 2xx, but Never Error makes every response count as success.
Workarounds:
- Leave Never Error off on nodes that notify your service, so failures show up in n8n.
- Respond quickly and do the work after you return
200. - Use an Error Trigger workflow in n8n to alert someone when a request fails.
How Hookdeck Can Help: Hookdeck accepts each request as soon as it arrives and stores it, so the node sees a fast 2xx even while your service is down. You then retry and replay requests on your own schedule, and issues alert you when delivery to your service starts failing.
Best practices
Use a credential, not a plain header
A credential lets you change the secret in one place for every node that uses it.
Agree a payload contract
Decide on an envelope with event, id and data, and use it in every workflow. A receiver with a known shape is easy to test and hard to break.
Fail closed
Return 401 on a missing or wrong header, and reject everything if your own secret is not configured.
Check headers case-insensitively
n8n lowercases header names by default. Most frameworks already treat header names case-insensitively, but if you read raw header names, compare them in lower case.
Acknowledge fast and process asynchronously
Return 200 after the header check and queue the work, so a slow job doesn't run into the node's timeout. See why to process webhooks asynchronously.
Make handlers idempotent
Processing the same request twice should have the same result as processing it once. See our guide to webhook idempotency.
Conclusion
Requests from n8n's HTTP Request node carry exactly what the workflow author configures, with no signature, event name or ID added by n8n. Attach a Header, Bearer or Basic auth credential to every node that calls your service, compare the full header value in constant time, and agree a payload with event and id fields so your receiver can route and deduplicate.
Hookdeck Event Gateway verifies that header and stops requests that fail it, stores every request before your service sees it, and retries on your terms, so a slow receiver doesn't turn into a failed workflow.
Get started with Hookdeck for free and handle n8n webhooks reliably in minutes.