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) orGET(some simple integrations) -
Path: a unique slug for this webhook (e.g.,
github-pr-merged) -
Response Mode:
Immediately(respond before processing) orLast 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]
}
]
}
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}'
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=...
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 } }];
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"
}
}
}
}
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]
}
]
}
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]
}
]
}
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) }];
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.













