Complete "Lesson 4: Transforming and Routing Data Between Services" first to unlock this lesson.Go to previous lesson

What Is a Webhook and Why It Matters

Most API integrations are pull-based: your workflow runs on a schedule, calls an external service, and fetches whatever changed since the last check. Webhooks flip that model. Instead of you asking, the external service pushes data to you the moment something happens — a payment completes, a form is submitted, a repo gets a new commit. The result is near-instant reaction with zero polling overhead.

n8n has a built-in Webhook node that turns any workflow into an HTTP endpoint. When that endpoint receives a request, the workflow fires immediately. This lesson walks through setting that up, handling the incoming payload, and sending a proper response back to the caller.

flow diagram comparing polling model (n8n repeatedly calls API) vs webhook model (API calls n8n once on event)

Setting Up the Webhook Node

Drag a Webhook node onto the canvas and set it as the trigger (first node). The two settings you configure immediately are the HTTP Method and the Path.

HTTP Method is almost always POST — that is what every major platform sends. Use GET only when the third party explicitly documents it (some verification handshakes use GET).

The Path is the unique slug that identifies this workflow. n8n generates a UUID by default, but you can set something readable like /stripe-events or /github-push. The full URL n8n shows you looks like this:

bash
https://your-n8n-instance.com/webhook/stripe-events

You paste that URL into the third-party platform's webhook settings — Stripe dashboard, GitHub repo settings, Typeform, whatever the service is.

n8n gives you two webhook URLs per node: a Test URL and a Production URL. The Test URL is active only while you are in the editor with the workflow open and listening. The Production URL is active only when the workflow is saved and activated. Never give a vendor your Test URL for a live integration — it stops working the moment you close the browser tab.

Receiving and Parsing the Payload

Once the webhook fires, the incoming data lands in the Webhook node's output. n8n automatically parses JSON bodies, so if Stripe sends a payment_intent.succeeded event, you access the charge amount directly in subsequent nodes as an expression:

javascript
{{ $json.data.object.amount }}

For form-encoded bodies (some older services send Content-Type: application/x-www-form-urlencoded), n8n also parses those automatically into the same $json object.

Headers are available too, which matters for signature verification. Stripe, GitHub, and most serious platforms sign their webhook payloads with an HMAC secret so you can confirm the request actually came from them. In n8n you can read the signature header inside a Code node and verify it:

javascript
const crypto = require('crypto');
const secret = 'your_webhook_secret';
const payload = $input.first().binary?.data
? Buffer.from($input.first().binary.data, 'base64').toString()
: JSON.stringify($input.first().json);
const signature = $input.first().headers['x-hub-signature-256'];
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expected) {
throw new Error('Invalid webhook signature');
}
return $input.all();

Always validate signatures on production workflows that trigger anything sensitive — sending emails, modifying records, charging users.

block diagram showing webhook node receiving request, passing to signature-check code node, then branching to process node or error node

Responding Back to the Caller

Some platforms require a specific HTTP response, not just the default 200 OK. Stripe expects a 200 within a few seconds or it retries. GitHub expects 200. Some systems like Typeform or custom internal services want a JSON body back confirming receipt.

In the Webhook node settings, set Response Mode to Last Node. Now the last node in your workflow controls what gets sent back. Pair it with a Respond to Webhook node at the end of the chain and configure it:

json
{
"statusCode": 200,
"body": { "received": true, "id": "={{ $json.id }}" }
}

If your workflow does heavy processing — database writes, sending emails, calling other APIs — and you still need to respond fast, use Response Mode: Immediately in the Webhook node. n8n returns 200 right away and continues running the rest of the workflow in the background. The trade-off is you cannot send a dynamic response body, but the caller never times out.

Common Mistakes to Avoid

Using the Test URL in production is the most frequent mistake. The workflow goes silent the moment you close the editor and nobody notices until events are lost.

Not handling retries is another gap. Stripe retries failed webhooks up to 72 hours. If your workflow is not idempotent — meaning running it twice on the same event causes a duplicate record or a double charge — you need to check whether you already processed a given event ID before doing any writes.

Ignoring the response timeout causes silent failures. If your workflow takes 30 seconds and the caller expects a response in 5, it marks the delivery as failed and retries, even though your workflow eventually succeeds. Either respond immediately or optimize the processing path.

Summary

Webhooks let third-party services push events to n8n in real time, removing the need for scheduled polling. You set up a Webhook node with a path and HTTP method, paste the Production URL into the vendor's settings, then access the payload through $json in downstream nodes. Signature verification protects against spoofed requests, and the Respond to Webhook node or Immediately mode gives you control over what the caller receives back. Getting these details right — correct URL, signature check, idempotency, response timing — is what separates a brittle integration from one that runs reliably in production.

Lesson practice

Lesson Checkpoint

Review what you learned and get feedback on your work.

0/7
01What is the key architectural difference between a webhook-based integration and a polling-based integration?
02In n8n, when should you use the Production Webhook URL instead of the Test URL?
03A third-party service sends a webhook with Content-Type: application/x-www-form-urlencoded. How does n8n handle this payload?
04What is the purpose of verifying the HMAC signature on an incoming webhook request?
05You need to respond to the caller with a custom JSON body containing a field from the incoming payload. Which Webhook node Response Mode should you use?
06Your workflow performs several slow database writes after receiving a webhook. The calling service times out after 5 seconds. What is the best Response Mode to prevent the caller from marking the delivery as failed?
07Stripe retries a webhook event because your endpoint returned an error the first time. Your workflow is not idempotent. What is the likely consequence?
Answered · 0/7
Lesson 5: Webhooks and Event-Driven Integrations