Webhook and HTTP Request in n8n: how a workflow receives and sends data
Every workflow has two doors to the world. Through a Webhook, another system "rings" n8n and hands it data; through HTTP Request, n8n "rings" someone else's API. We learn both — with test and production URLs, protection (key and signature), a synchronous reply and pagination — so you can build a small API of your own without a line of code.
$credentials.apiKey was removed — reading secrets from an expression like that is not described in the documentation; keys live in a credential. (7) "Retry On Fail" is not an HTTP Request option but a node setting (Settings tab). Added: webhook protection with an HMAC signature, IP allowlist, "Only Run If", JWT, "Import cURL", the credential types, limits of Respond to Webhook. Removed: examples with real internal hosts, names and a Postgres database (the exercise now runs without an external database), and the link to a "CRM" on an internal address.
01What you'll learn
- How the Webhook node works and the difference between a test and a production URL.
- How to protect the entrance — with a key in a header, Basic, JWT, an IP allowlist and an HMAC signature.
- How to return a synchronous reply with Respond to Webhook (a small API on n8n).
- How the HTTP Request node calls someone else's API — method, headers, body, authentication.
- How keys are kept (credentials) and how to import a request from curl.
- How to collect all pages from an API with pagination and how to guard against errors.
02Before you start
- You have done Lesson 1 — you have a working n8n 2.x installation 🔒 local and have built your first workflow.
- A terminal with
curl(available on Linux, macOS and modern Windows). For the signature below you also needopenssl. - For the production URL from outside (when a third-party service has to call you), n8n must be reachable over HTTPS — that is a separate topic; in this lesson we test on your own machine.
03Steps
-
Two doors: inbound and outbound
Webhook is the entrance: it is a "trigger" — it starts the workflow when someone sends it a request. HTTP Request is the exit: an ordinary node that sends a request to an address you choose. With the two you can connect n8n to any service, even one without a ready-made node. Why does it matter? Most integrations are exactly this — "when X happens, call Y".
-
Webhook: test and production URL
Every Webhook node has two URLs, which n8n shows at the top of its panel:
URL How it is switched on How long it lives Data is visible Test /webhook-test/…you press Listen for test event (or Execute workflow if the workflow is not published) 120 seconds in the editor, immediately Production /webhook/…you publish the workflow (Publish) until you unpublish it only in the Executions tab bash · test call (local)# 1) In n8n: open the Webhook node → Listen for test event # 2) Then, within 120 seconds: curl -X POST http://localhost:5678/webhook-test/intake \ -H "Content-Type: application/json" \ -d '{"name": "Maria", "event": "form_submit"}'The path
intakeis an example — by default n8n gives you a random one to avoid clashes. The production URL looks likehttps://<your-n8n-host>/webhook/intake. The first part of the path (webhook,webhook-test) is an installation setting; if n8n sits behind a reverse proxy, set its address withN8N_WEBHOOK_URL, otherwise n8n will show wrong URLs.✅Rules that save hoursThe test URL accepts one call per press of "Listen". The production URL does not show data in the editor — look in Executions. n8n allows one webhook per "path + method" pair; if you get a message that the path is taken, change the path or unpublish the other workflow. The node accepts a body up to 16 MiB (for more:N8N_PAYLOAD_SIZE_MAXin your installation). -
The Webhook node settings
Field What you choose Advice HTTP Method DELETE, GET, HEAD, PATCH, POST, PUT POST for data, GET for parameters in the URL. For several methods on one path: Settings → Allow Multiple HTTP Methods Path free text, may include a parameter: /user/:iddescriptive, for example contact-intakeAuthentication None · Basic auth · Header auth · JWT auth for a production URL — never "None" Respond Immediately · When Last Node Finishes · Using 'Respond to Webhook' Node · Streaming response for a small API choose Using 'Respond to Webhook' Node Response Code 200, 201… (only with "Immediately" and "When Last Node Finishes") with the Respond to Webhook node the code is set in that node Under Add Option there are more useful things: IP(s) Allowlist (a request from another address gets a 403 error), Ignore Bots (ignores link previewers and crawlers), Only Run If (an expression on
{ body, headers, params, query }; if it isfalse, the workflow does not start), Allowed Origins (CORS) — for browser requests, and Raw Body — for the raw body (we need it for the signature in step 5).⚠️IP restriction behind a proxyIf n8n sits behind a reverse proxy and the IP list does not work, setN8N_PROXY_HOPSto the number of proxies in front of n8n (per the node's documentation). -
Protecting with a key: Header auth, Basic and JWT
In the Webhook node's Authentication field you choose a method and create a credential — the key lives there, not in the node and not in the text of the workflow.
bash · generate your own keyopenssl rand -hex 32n8n · Webhook nodeHTTP Method: POST Path: intake Authentication: Header Auth → Credential: (create new) Name: X-Api-Key Value: <your-generated-key>bash · call with the keyKEY="<your-generated-key>" curl -X POST http://localhost:5678/webhook-test/intake \ -H "Content-Type: application/json" \ -H "X-Api-Key: $KEY" \ -d '{"source": "website", "email": "test@example.com"}'Without the key (or with a wrong one) n8n rejects the request and the workflow does not start — try that too, to see it for yourself. ⚠️ The exact rejection code is not stated in the documentation.
Basic auth is a username and password (
curl -u "user:password" …). JWT auth uses a "JWT" credential — a passphrase with an HMAC algorithm (for example HS256) or a PEM key; the caller sends a signed JWT in theAuthorizationheader. ⚠️ We did not confirm the exact header form for JWT — see the Webhook credentials page. -
HMAC signature: proof that a request comes from a trusted sender
A key in a header is like a password that is visible inside the request. A signature is stronger: the sender and n8n share a secret that never travels. The sender computes a "fingerprint" (HMAC-SHA256) of the body with that secret and sends it in a header; n8n computes the same and compares. If the body was changed on the way, the fingerprints differ.
bash · sender sideSECRET="<shared-secret>" BODY='{"event":"order.created","id":42}' SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //') curl -X POST http://localhost:5678/webhook-test/orders \ -H "Content-Type: application/json" \ -H "X-Signature: sha256=$SIG" \ -d "$BODY"In n8n the chain is: Webhook (turn on Raw Body in the options) → Crypto (action Hmac, type SHA256, encoding HEX; the secret is in a "Crypto" credential, field Hmac Secret) → IF, which compares
sha256=+ the computed value with{{ $('Webhook').item.json.headers['x-signature'] }}. Mismatch → Respond to Webhook with code 401 and stop; match → carry on.⚠️Two places we did not confirm(1) Where exactly the raw body appears when Raw Body is on (as a value or as a binary field) — look in the Webhook's Output panel and, in the Crypto node, choose "Binary File" if it is binary. The signature must cover the same bytes the sender signed, not a re-serialised JSON. (2) Safe comparison: a plain string comparison is enough for a learning example; for strict protection use a constant-time comparison (crypto.timingSafeEqualin a Code node) — on self-hosted you must allow thecryptomodule in the Code node, while on n8n Cloudcryptois available. We have not run that code.✅Layers, not a single keyGood practice: Header auth or JWT plus a signature for important entrances, an IP list when the sender has a fixed address, and HTTPS. Change the secret if you suspect it has leaked. -
Where the data is in the request
The Webhook node returns one object field. The most common mistake is to look for the data directly in
$json.expressions in n8n{{ $json.body.email }} // body field (POST) {{ $json.headers['x-api-key'] }} // header (lowercase) {{ $json.query.page }} // URL parameter (?page=2) {{ $json.params.id }} // part of the path (/user/:id) -
Respond to Webhook: reply like a real API
In the Webhook node set Respond → Using 'Respond to Webhook' Node, and place the Respond to Webhook node where you want to "close" the request — after the data is ready. That way the caller gets a reply within seconds and sees the result.
Webhook (POST)→IF · validate→Edit Fields→Respond to WebhookRespond With chooses what is returned: JSON (you write the body), First Incoming Item, All Incoming Items, Text, Binary File, Redirect, JWT Token or No Data. The options give Response Code and Response Headers.
✅How Respond to Webhook behavesIt runs once, for the first item; a second such node after the first is ignored. If the workflow ends without reaching it, the caller gets a standard message with 200; if there is an error before it — a 500 reply. To return several records in one reply use "All Incoming Items" or the Aggregate node first.⚠️HTML replies from a webhookSince n8n 1.103.0, HTML returned from a webhook is placed in a sandboxed iframe: scripts cannot reach the page, relative URLs do not work — use full URLs. -
HTTP Request: n8n calls someone else's API
Add the HTTP Request node. The main fields:
Field What it is Method GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS URL the full address; may be an expression Authentication None · Predefined Credential Type · Generic Credential Type Send Query Parameters adds ?key=value— "Using Fields Below" or "Using JSON"Send Headers headers such as Accept; do not put secrets hereSend Body types: JSON · Form-Data · Form URL Encoded · n8n Binary File · Raw n8n · GET with parametersMethod: GET URL: https://api.example.com/users Send Query Parameters: on page = {{ $json.page ?? 1 }} limit = 50 status = active # Result: /users?page=1&limit=50&status=activen8n · POST with a JSON bodyMethod: POST URL: https://api.example.com/leads Authentication: Generic Credential Type → Bearer Auth # the key is in a credential Send Body: on · Body Content Type: JSON · Specify Body: Using JSON { "email": "{{ $json.body.email }}", "name": "{{ $json.body.name }}", "source": "n8n-workflow", "date": "{{ $now.toISO() }}" }Tip: if a service's documentation gives a
curlexample, press Import cURL in the node and paste the command — n8n fills in the fields. Values arrive as text; for numbers and booleans switch to "Using JSON". -
Authentication and credentials
The node first offers Predefined Credential Type — if n8n already knows the service (for example Google, Asana) this is the easiest. For the rest you use Generic Credential Type:
Type When How Bearer Auth JWT and OAuth access keys header Authorization: Bearer …, assembled by n8nHeader Auth a key in a custom header name + value, for example X-API-KeyQuery Auth older APIs with a key in the URL one name/value parameter Basic Auth · Digest Auth username and password n8n builds the header OAuth1 · OAuth2 services with "sign in with…" n8n runs the flow and refreshes access Custom Auth several headers/parameters at once JSON with headers,qs,bodySimplified Custom Auth like Custom, but with a template and fields from n8n 2.35.0 onwards ✅Keys — only in credentialsDo not write keys into the header, parameter or body fields: they stay readable in the workflow and in its shared copies. A credential is stored encrypted by n8n. Give every separate key its own name and the least permissions it needs. -
Options that make the node reliable
- Timeout — how long the node waits for the start of the response; in the node's source code it is 10,000 ms. For slow APIs raise it explicitly.
- Response → Include Response Headers and Status — returns both the code and the headers; Never Error — does not fail on a non-2xx code, so you can handle the error yourself.
- Batching — if you feed in many items, it sends them in batches (by default 50 at a time with a 1,000 ms pause) so the API does not block you.
- Redirects, Proxy, Lowercase Headers — following redirects, a proxy and lowercase header names.
- Ignore SSL Issues (Insecure) — only for tests on your own network with a self-signed certificate; never for public APIs.
Retrying is not an option here but a node setting: Settings → Retry On Fail (and On Error for what to do on failure). That gives an unstable API a second chance without writing any logic.
-
Pagination: all pages of an API
When an API returns results in parts, turn on Add Option → Pagination. There are two modes:
n8n · by page numberPagination Mode: Update a Parameter in Each Request Type: Query Name: page Value: {{ $pageCount + 1 }} # $pageCount starts at 0 Pagination Complete When: Response Is Empty Limit Pages Fetched: on · Max Pages: 100n8n · by next-page URLPagination Mode: Response Contains Next URL Next URL: {{ $response.body["next-page"] }} # the name depends on your API Pagination Complete When: Other Complete Expression: {{ !$response.body["next-page"] }}Available are
$pageCount,$requestand$response(.body,.headers,.statusCode). The end condition can be "empty response", a specific response code or an expression. First see how exactly the API paginates — with one request without pagination — and set Max Pages as a safeguard so the loop does not run forever. There is also an optional pause between requests (Interval Between Requests). -
Exercise: an intake point for a form
You build a small API: it receives data from a contact form, checks the email and returns a reply. No external database — so the exercise runs anywhere.
Webhook POST→IF · email valid?→ yesEdit Fields→Respond 201n8n · settingsWebhook: POST · Path: contact-intake · Authentication: Header Auth Respond: Using 'Respond to Webhook' Node IF: Value 1: {{ $json.body.email }} Operation: String → matches regex Value 2: ^[^\s@]+@[^\s@]+\.[^\s@]+$ Edit Fields ("true" output): ok = true receivedAt = {{ $now.toISO() }} email = {{ $json.body.email }} Respond to Webhook (after Edit Fields): Respond With: JSON · Response Code: 201 Body: { "ok": true, "receivedAt": "{{ $json.receivedAt }}" } Respond to Webhook ("false" output): Respond With: JSON · Response Code: 400 Body: { "ok": false, "error": "invalid email" }bash · test (with "Listen for test event" pressed)KEY="<your-generated-key>" curl -i -X POST http://localhost:5678/webhook-test/contact-intake \ -H "Content-Type: application/json" -H "X-Api-Key: $KEY" \ -d '{"email": "maria@example.com", "name": "Maria", "message": "Hello"}'Also try
"email": "invalid"— you should see a 400 code. Then publish the workflow and repeat with the production URL (/webhook/contact-intake). If you want to keep the data, add a node for your own table or database after "Edit Fields". -
When something does not work
❌404 on a WebhookMost often the URL does not match the state: a test URL without "Listen" pressed (or after 120 seconds), or a production URL while the workflow is not published. Also check that the path and method match the setting.❌The data "does not arrive"You are looking in$jsoninstead of$json.body. Headers are in$json.headers, URL parameters in$json.query.❌Timeout in HTTP RequestSet a larger Timeout in the options; for an unstable API turn on Retry On Fail in Settings.❌SSL certificate errorFix the certificate first. Ignore SSL Issues is only for tests on your own network.❌The webhook reply is "Workflow got started"The Webhook is set to Respond → Immediately. Change it to "Using 'Respond to Webhook' Node" or "When Last Node Finishes".💡Long processesIf the reply takes minutes, return "accepted" at once and offer a second webhook for checking the result; on n8n Cloud a request with no reply within 100 seconds fails with a 524 error.
04Check
Checklist
- The test URL answers only while you are listening; the production one — after publishing.
- The Webhook is protected; a request without the key does not start the workflow.
- Data is read from
$json.body,headers,query,params. - Respond to Webhook returns 201 for a good request and 400 for a bad email.
- Keys are in credentials, not in node fields.
- HTTP Request has a clear timeout; pagination has an end and "Max Pages".
- There are no real keys in your workflow or screenshots.
Quiz
1. You press "Listen for test event" and send a request to the URL 3 minutes later. What happens?
2. Where is the "email" field from the body of a POST request to the Webhook node?
3. Where is the right place for an API key used by an HTTP Request node?
4. What is the HMAC signature of a webhook for?
05What's next
06Sources
- n8n: the Webhook node — URLs, methods, authentication, options; also: workflow development and common issues.
- n8n: Webhook credentials · JWT credentials.
- n8n: Respond to Webhook — reply modes and behaviour.
- n8n: the HTTP Request node and its credentials.
- n8n: pagination in HTTP Request.
- n8n: the Crypto node (Hmac) · the Code node.
- n8n on GitHub: the HTTP Request node description — default values of the options.