The KAGAMI mark КАГАМИ
kagami.bg/academy · lesson · machine-readable viewVERIFIED 2026-10-01 · UPDATED 2026-10-01
IDENTITY
module
n8n-02 · Webhook and HTTP Request nodes
series
n8n · lesson 2 of 10
level
Intermediate
duration
about 1 h
prerequisites
n8n lesson 1 (a running n8n instance and a first workflow)
trust_label
VERIFIED 2026-10-01 (against docs.n8n.io Webhook, Respond to Webhook, HTTP Request, credentials and Crypto pages, and the HTTP Request node source on GitHub; stable release read from the npm registry) · UPDATED 2026-10-01 · NOT TESTED end to end (no n8n instance was run for this edit; the HMAC digest command was run locally in a shell only)
versions
n8n 2.x (stable 2.41.x at the check date) · workflows are "published" in 2.x to register the production webhook
language
human view: english edition · bulgarian edition: /academy/n8n/ (lesson 2)
previous / next
n8n 01 · Installation and first workflow / n8n 03 · AI Agent node with Ollama
PURPOSE

Teach the two doors of an n8n workflow: the Webhook node (inbound: another system calls n8n) and the HTTP Request node (outbound: n8n calls an API). Cover test versus production webhook URLs, webhook authentication (Basic, Header, JWT) plus an HMAC signature check, the Respond to Webhook node for synchronous replies, HTTP Request authentication through credentials, node options and pagination, and a small hands-on intake endpoint.

KEY CONCEPTS
COMMANDS / PATHS
CHECKLIST
NEXT MODULE

n8n 03 · AI Agent node with Ollama · a local model inside a workflow with tool calling and memory · bridge: Quick experiment (kagami.bg/stalbata/)

SOURCES
TAGS
n8nwebhookhttp-requestrest-apiauthenticationhmacpaginationautomation
VERIFIED · 01.10.2026 UPDATED · 01.10.2026

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.

⏱ about 1 h Intermediate n8n · Lesson 2/10 Webhook · HTTP Request · API
n8n (your own installation)🔒 local External APIs you call🌐 global
🔄
UPDATED · 01.10.2026 — what changed
Checked against the n8n 2.x documentation (the stable version today is 2.41). Fixed: (1) In 2.x the production Webhook URL is switched on when the workflow is published ("Publish"), not "activated"; the test URL listens for only 120 seconds after "Listen for test event". (2) The setting is called Respond (not "Response Mode") and has four values; "Response Code" is not available when you use the Respond to Webhook node — the code is set in that node. (3) Webhook authentication is Basic, Header and JWT (JWT was missing in the old version). (4) The pagination in the old text was made up: the fields "Initial Value", "Increment By" and "Stop Condition" do not exist. The real ones are "Update a Parameter in Each Request" and "Response Contains Next URL", and the end is set with "Pagination Complete When". (5) The old claim "no timeout by default" is wrong — in the node's source code the default is 10,000 ms. (6) The expression $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.
⚠️
What we have not run ourselves
For this check we did not run an n8n instance. The settings are checked against the documentation and the HTTP Request node's source code and are not tested end to end — so there is no "TESTED" label. Only the command that computes the HMAC signature was run locally. ⚠️ marks specific places we could not confirm.

01What you'll learn

02Before you start

⛔
A webhook is an open door
Anyone who knows the address can call it. The address is not a secret. That is why the entrance is protected (steps 4 and 5), and in the examples here you should always replace the keys with your own, freshly generated ones.

03Steps

  1. 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".

  2. Webhook: test and production URL

    Every Webhook node has two URLs, which n8n shows at the top of its panel:

    URLHow it is switched onHow long it livesData is visible
    Test /webhook-test/…you press Listen for test event (or Execute workflow if the workflow is not published)120 secondsin the editor, immediately
    Production /webhook/…you publish the workflow (Publish)until you unpublish itonly 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 intake is an example — by default n8n gives you a random one to avoid clashes. The production URL looks like https://<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 with N8N_WEBHOOK_URL, otherwise n8n will show wrong URLs.

    ✅
    Rules that save hours
    The 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_MAX in your installation).
  3. The Webhook node settings

    FieldWhat you chooseAdvice
    HTTP MethodDELETE, GET, HEAD, PATCH, POST, PUTPOST for data, GET for parameters in the URL. For several methods on one path: Settings → Allow Multiple HTTP Methods
    Pathfree text, may include a parameter: /user/:iddescriptive, for example contact-intake
    AuthenticationNone · Basic auth · Header auth · JWT authfor a production URL — never "None"
    RespondImmediately · When Last Node Finishes · Using 'Respond to Webhook' Node · Streaming responsefor a small API choose Using 'Respond to Webhook' Node
    Response Code200, 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 is false, 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 proxy
    If n8n sits behind a reverse proxy and the IP list does not work, set N8N_PROXY_HOPS to the number of proxies in front of n8n (per the node's documentation).
  4. 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 key
    openssl rand -hex 32
    n8n · Webhook node
    HTTP Method:     POST
    Path:            intake
    Authentication:  Header Auth
      → Credential:  (create new)
         Name:       X-Api-Key
         Value:      <your-generated-key>
    bash · call with the key
    KEY="<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 the Authorization header. ⚠️ We did not confirm the exact header form for JWT — see the Webhook credentials page.

  5. 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 side
    SECRET="<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.timingSafeEqual in a Code node) — on self-hosted you must allow the crypto module in the Code node, while on n8n Cloud crypto is available. We have not run that code.
    ✅
    Layers, not a single key
    Good 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.
  6. 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)
  7. 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 Webhook

    Respond 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 behaves
    It 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 webhook
    Since 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.
  8. HTTP Request: n8n calls someone else's API

    Add the HTTP Request node. The main fields:

    FieldWhat it is
    MethodGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
    URLthe full address; may be an expression
    AuthenticationNone · Predefined Credential Type · Generic Credential Type
    Send Query Parametersadds ?key=value — "Using Fields Below" or "Using JSON"
    Send Headersheaders such as Accept; do not put secrets here
    Send Bodytypes: JSON · Form-Data · Form URL Encoded · n8n Binary File · Raw
    n8n · GET with parameters
    Method: 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=active
    n8n · POST with a JSON body
    Method: 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 curl example, 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".

  9. 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:

    TypeWhenHow
    Bearer AuthJWT and OAuth access keysheader Authorization: Bearer …, assembled by n8n
    Header Autha key in a custom headername + value, for example X-API-Key
    Query Autholder APIs with a key in the URLone name/value parameter
    Basic Auth · Digest Authusername and passwordn8n builds the header
    OAuth1 · OAuth2services with "sign in with…"n8n runs the flow and refreshes access
    Custom Authseveral headers/parameters at onceJSON with headers, qs, body
    Simplified Custom Authlike Custom, but with a template and fieldsfrom n8n 2.35.0 onwards
    ✅
    Keys — only in credentials
    Do 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.
  10. 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.

  11. 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 number
    Pagination 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: 100
    n8n · by next-page URL
    Pagination 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, $request and $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).

  12. 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?
    → yes
    Edit Fields
    →
    Respond 201
    n8n · settings
    Webhook:  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".

  13. When something does not work

    ❌
    404 on a Webhook
    Most 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 $json instead of $json.body. Headers are in $json.headers, URL parameters in $json.query.
    ❌
    Timeout in HTTP Request
    Set a larger Timeout in the options; for an unstable API turn on Retry On Fail in Settings.
    ❌
    SSL certificate error
    Fix 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 processes
    If 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

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

  1. n8n: the Webhook node — URLs, methods, authentication, options; also: workflow development and common issues.
  2. n8n: Webhook credentials · JWT credentials.
  3. n8n: Respond to Webhook — reply modes and behaviour.
  4. n8n: the HTTP Request node and its credentials.
  5. n8n: pagination in HTTP Request.
  6. n8n: the Crypto node (Hmac) · the Code node.
  7. n8n on GitHub: the HTTP Request node description — default values of the options.