DEV Community

Alex Kane
Alex Kane

Posted on

The Complete n8n Webhook Guide: Receive, Process, and Respond to Any Event (2026)

Webhooks are the backbone of modern automation. Any time Stripe processes a payment, GitHub merges a PR, Typeform gets a submission, or Calendly books a meeting — it fires a webhook. n8n can catch every one of them and trigger whatever workflow you need.

This guide covers everything: setting up the webhook trigger, testing it locally, processing the payload, sending a response, and securing it with signature verification. Full workflow JSON included for common use cases.


What Is a Webhook in n8n?

A webhook is an HTTP POST request that an external service sends to a URL you provide, containing event data as JSON. In n8n, the Webhook node creates a unique URL that listens for these incoming requests and triggers your workflow.

Unlike polling (checking a service every N minutes), webhooks are instant — your workflow runs the moment the event happens.


Step 1: Set Up the Webhook Trigger

Add a Webhook node as the first node in your workflow.

Key settings:

  • HTTP Method: POST (most services) or GET (some simple integrations)
  • Path: a unique slug for this webhook (e.g., github-pr-merged)
  • Response Mode: Immediately (respond before processing) or Last Node (respond after workflow completes)
  • Authentication: None, Basic Auth, or Header Auth

n8n gives you two URLs:

  • Test URL — active only when you click "Listen for Test Event" in the editor. Use this while building.
  • Production URL — active when the workflow is activated. Use this for live services.
{
  "nodes": [
    {
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "my-webhook",
        "responseMode": "onReceived",
        "responseData": "allEntries",
        "options": {}
      },
      "position": [240, 300]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Test Your Webhook

While the webhook node is in "Listen" mode (click the play button on the node), you can trigger it with curl:

curl -X POST https://your-n8n.com/webhook-test/my-webhook \
  -H "Content-Type: application/json" \
  -d '{"event": "order.created", "order_id": "123", "amount": 49.99}'
Enter fullscreen mode Exit fullscreen mode

n8n will show you the received data immediately. The entire JSON payload is available as $json in subsequent nodes.


Step 3: Access Webhook Data

After the webhook node, the incoming data is available via n8n expressions:

$json.body.event          → "order.created"
$json.body.order_id       → "123"
$json.headers["x-api-key"] → the API key header
$json.query.source        → query string param ?source=...
Enter fullscreen mode Exit fullscreen mode

In a Code node, you access it like this:

const body = $input.first().json.body;
const event = body.event;
const orderId = body.order_id;
const amount = parseFloat(body.amount);
return [{ json: { event, orderId, amount, processed: true } }];
Enter fullscreen mode Exit fullscreen mode

Step 4: Send a Response

External services usually expect a response within a few seconds or they'll retry. Configure your webhook node's response:

Option A — Respond immediately, process async

Set Response Mode: Immediately. n8n responds with 200 OK right away and continues processing the workflow. Best for services that just need acknowledgment (GitHub, Stripe, etc.).

Option B — Respond with processed data

Set Response Mode: Last Node. n8n waits for your workflow to complete, then sends the last node's output as the response body. Use for API-style webhooks where the caller needs a result.

Option C — Custom response

Use the Respond to Webhook node to return a specific status code and body at any point in your workflow:

{
  "name": "Respond to Webhook",
  "type": "n8n-nodes-base.respondToWebhook",
  "parameters": {
    "options": {
      "responseCode": 200,
      "responseData": "{"status": "received", "id": "{{$json.order_id}}"}",
      "responseHeaders": {
        "Content-Type": "application/json"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Complete Example: GitHub PR Merged → Slack + Sheets

When a GitHub PR is merged, notify Slack and log it to a Google Sheet.

{
  "name": "GitHub PR Merged",
  "nodes": [
    {
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "github-pr",
        "responseMode": "onReceived"
      },
      "position": [240, 300]
    },
    {
      "name": "Filter Merged",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [{
            "value1": "={{$json.body.action}}",
            "operation": "equals",
            "value2": "closed"
          }, {
            "value1": "={{$json.body.pull_request.merged}}",
            "operation": "equals",
            "value2": "true"
          }]
        }
      },
      "position": [460, 300]
    },
    {
      "name": "Slack Notify",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "operation": "postMessage",
        "channel": "#engineering",
        "text": "✅ PR merged: {{$json.body.pull_request.title}} by {{$json.body.pull_request.user.login}}"
      },
      "position": [680, 200]
    },
    {
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "sheetId": "YOUR_SHEET_ID",
        "columns": {
          "value": {
            "pr_title": "={{$json.body.pull_request.title}}",
            "author": "={{$json.body.pull_request.user.login}}",
            "merged_at": "={{$json.body.pull_request.merged_at}}",
            "repo": "={{$json.body.repository.full_name}}"
          }
        }
      },
      "position": [680, 400]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

In GitHub: go to your repo → Settings → Webhooks → Add webhook. Paste the n8n production URL, set Content-Type to application/json, choose "Pull requests" events.


Complete Example: Stripe Payment → CRM + Email

When a Stripe payment succeeds, add the customer to your CRM and send a receipt email.

{
  "name": "Stripe Payment Success",
  "nodes": [
    {
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "stripe-payments",
        "responseMode": "onReceived"
      },
      "position": [240, 300]
    },
    {
      "name": "Verify Signature",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const crypto = require('crypto');\nconst secret = 'whsec_YOUR_SECRET';\nconst payload = $input.first().json.rawBody;\nconst sig = $input.first().json.headers['stripe-signature'];\nconst timestamp = sig.split(',').find(p => p.startsWith('t=')).split('=')[1];\nconst signed = crypto.createHmac('sha256', secret).update(`${timestamp}.${payload}`).digest('hex');\nconst expected = sig.split(',').find(p => p.startsWith('v1=')).split('=')[1];\nif (signed !== expected) throw new Error('Invalid signature');\nconst event = JSON.parse(payload);\nreturn [{ json: event }];"
      },
      "position": [460, 300]
    },
    {
      "name": "Filter Paid",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [{"value1": "={{$json.type}}", "operation": "equals", "value2": "payment_intent.succeeded"}]
        }
      },
      "position": [680, 300]
    },
    {
      "name": "Send Receipt",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "to": "={{$json.data.object.receipt_email}}",
        "subject": "Payment confirmed — thank you!",
        "message": "Hi,\n\nYour payment of ${{($json.data.object.amount/100).toFixed(2)}} has been received.\n\nThank you for your purchase!",
        "messageType": "text"
      },
      "position": [900, 300]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Securing Webhooks: Signature Verification

Never trust a webhook without verifying it came from the right source. Most services (Stripe, GitHub, Shopify) sign their payloads with HMAC-SHA256.

Generic HMAC verification in n8n Code node:

const crypto = require('crypto');
const secret = 'your-webhook-secret';
const rawBody = $input.first().json.rawBody;  // requires n8n v1.x with raw body enabled
const receivedSig = $input.first().json.headers['x-hub-signature-256'];
const expectedSig = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

if (receivedSig !== expectedSig) {
  throw new Error('Webhook signature mismatch — request rejected');
}

return [{ json: JSON.parse(rawBody) }];
Enter fullscreen mode Exit fullscreen mode

Enable raw body parsing in your n8n webhook node settings under Options → Raw Body.


Common Webhook Patterns

Service Event to catch What to do
Stripe payment_intent.succeeded Send receipt, update CRM, grant access
GitHub pull_request.closed + merged=true Notify Slack, log to Sheets
Typeform form_response Save to Sheets, send follow-up email
Calendly invitee.created Add to CRM, send prep email
Shopify orders/create Notify fulfillment, update inventory
WooCommerce woocommerce_order_status_changed Trigger shipping, send confirmation

Ready-to-Use Webhook Workflows

If you'd rather start with a working template than build from scratch, the full n8n workflow JSONs for the patterns above (and 10 more) are available at stripeai.gumroad.com.

Individual templates start at $12. The full FlowKit bundle with 15 workflows is $97.


Summary

  • Test URL = for building, Production URL = for live traffic
  • Access payload via $json.body, headers via $json.headers
  • Respond immediately to avoid timeouts; process async if needed
  • Always verify HMAC signatures from production services
  • Use IF nodes to filter the specific event type you care about

Webhooks unlock real-time automation. Once you've wired one up, you'll find yourself connecting everything.

Top comments (0)