Webhooks are the backbone of modern automation. Instead of constantly checking for updates, you get notified automatically when something happens. In this guide, we explain webhooks from scratch: what they are, how they work, and how to use them in practice.

What is a Webhook?

Simple Explanation:

A webhook is like a doorbell. Instead of checking the door every 5 minutes (polling), the visitor rings the bell and you get notified immediately. Webhooks are a core concept in API integration and essential for real-time automation.

Technical:

A webhook is an HTTP POST request that a service sends to your URL when an event occurs.

Polling vs. Webhook

AspectPollingWebhook
InitiatorYou askService notifies you
LatencyMinutes (depending on interval)Real-time (seconds)
ResourcesMany unnecessary requestsOnly on events
ComplexityEasier to set upRequires public URL

How Do Webhooks Work?

The Flow

1. You register your URL with the service
        |
2. Event occurs (e.g., new order)
        |
3. Service sends POST request to your URL
        |
4. Your server receives the data
        |
5. You process the data
        |
6. You respond with 200 OK

A Typical Webhook Request

POST /webhook/orders HTTP/1.1
Host: your-domain.com
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...

{
  "event": "order.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "orderId": "12345",
    "customerEmail": "customer@example.com",
    "total": 99.00
  }
}

Creating a Webhook Endpoint

Option 1: n8n Webhook

Step 1: Add Webhook Node

  1. Create new workflow
  2. Add "Webhook" node
  3. Configuration:
    • HTTP Method: POST
    • Path: orders (becomes /webhook/orders)
    • Response Mode: When Last Node Finishes

Step 2: Copy URL

n8n shows two URLs:

  • Test URL: For development (only when workflow is open)
  • Production URL: For live operation (after activation)

Test: https://n8n.your-domain.com/webhook-test/orders
Prod: https://n8n.your-domain.com/webhook/orders

Step 3: Process Data

// Node: Code
// Process webhook data

const event = $json.body.event;
const data = $json.body.data;

console.log(`Event: ${event}`);
console.log(`Order ID: ${data.orderId}`);

return {
  orderId: data.orderId,
  email: data.customerEmail,
  total: data.total,
  processedAt: new Date().toISOString()
};

Option 2: Make.com Webhook

Step 1: Create Custom Webhook

  1. New scenario
  2. Trigger: "Webhooks" -> "Custom webhook"
  3. Click "Add" -> Give it a name
  4. Copy URL

https://hook.eu1.make.com/abc123xyz456

Step 2: Define Structure

Either:

  • Automatically on first request
  • Or manually in "Data structure" tab

Option 3: Your Own Server (Node.js)

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/orders', (req, res) => {
  const { event, data } = req.body;

  console.log('Webhook received:', event);

  // Here: Process data
  processOrder(data);

  // Always respond with 200 OK (quickly!)
  res.status(200).json({ received: true });
});

app.listen(3000);

Securing Webhooks

1. Signature Verification

Most services sign their webhooks:

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return computed === signature;
}

// In your webhook handler:
const isValid = verifySignature(
  JSON.stringify(req.body),
  req.headers['x-webhook-signature'],
  process.env.WEBHOOK_SECRET
);

if (!isValid) {
  return res.status(401).json({ error: 'Invalid signature' });
}

2. IP Whitelisting

Only accept requests from known IPs:

const allowedIPs = ['192.168.1.1', '10.0.0.1'];

if (!allowedIPs.includes(req.ip)) {
  return res.status(403).json({ error: 'IP not allowed' });
}

3. Use HTTPS

Webhooks should ALWAYS run over HTTPS, never HTTP.

4. Timestamp Validation

Reject old requests (prevent replay attacks):

const timestamp = parseInt(req.headers['x-webhook-timestamp']);
const now = Math.floor(Date.now() / 1000);

if (now - timestamp > 300) { // Older than 5 minutes
  return res.status(400).json({ error: 'Request too old' });
}

Practical Examples

Example 1: Shopify Order Webhook

Register webhook (Shopify Admin):

  1. Settings -> Notifications -> Webhooks
  2. "Create webhook" -> Event: "Order creation"
  3. URL: Your n8n/Make.com URL
  4. Format: JSON

Payload example:

{
  "id": 820982911946154500,
  "email": "customer@example.com",
  "created_at": "2024-01-15T10:30:00+01:00",
  "total_price": "199.00",
  "line_items": [
    {
      "title": "Product A",
      "quantity": 2,
      "price": "99.50"
    }
  ]
}

n8n Workflow:

Shopify Webhook
        |
    Extract data
        |
    Parallel:
    |-- Google Sheets: Log order
    |-- Email: Send confirmation
    +-- Slack: Notify team

For a complete Stripe integration walkthrough, see our dedicated Stripe webhook setup guide.

Example 2: Stripe Payment Webhook

Register webhook:

# Stripe Dashboard or CLI
stripe webhooks create \
  --url https://n8n.your-domain.com/webhook/stripe \
  --events payment_intent.succeeded,payment_intent.failed

Process payload:

// Node: Switch - By event type
const eventType = $json.type;

switch(eventType) {
  case 'payment_intent.succeeded':
    return { route: 'success' };
  case 'payment_intent.failed':
    return { route: 'failed' };
  default:
    return { route: 'ignore' };
}

Example 3: GitHub Push Webhook

Set up webhook:

  1. Repository -> Settings -> Webhooks -> Add webhook
  2. URL: Your webhook URL
  3. Content type: application/json
  4. Events: Push events

Usage:

GitHub Push Webhook
        |
    Branch = "main"?
        | Yes
    SSH: Run deploy script
        |
    Slack: Notify about deployment

Example 4: Typeform Response Webhook

Enable webhook:

  1. Typeform -> Connect -> Webhooks
  2. Enter URL
  3. Send test

Use data:

// Typeform sends answers in structured format
const answers = $json.form_response.answers;

const name = answers.find(a => a.field.ref === 'name')?.text;
const email = answers.find(a => a.field.ref === 'email')?.email;
const message = answers.find(a => a.field.ref === 'message')?.text;

return { name, email, message };

Webhook Debugging

Local Development

Problem: Webhooks need a public URL.

Solution 1: ngrok

# Install ngrok
brew install ngrok

# Start tunnel
ngrok http 5678

# Shows public URL
# https://abc123.ngrok.io -> localhost:5678

Solution 2: Stripe CLI

stripe listen --forward-to localhost:5678/webhook/stripe

Request Inspection

Tool: RequestBin / Webhook.site

  1. Open https://webhook.site
  2. Copy unique URL
  3. Use as webhook endpoint
  4. All requests are displayed

n8n Debug

  1. Open workflow
  2. Activate "Test Workflow"
  3. Send webhook
  4. Inspect data in each node

Error Handling

Retries

If your endpoint fails, many services retry:

ServiceRetry Behavior
Stripe3 days, exponential backoff
Shopify19 attempts, 48 hours
GitHub3 attempts

Idempotency

Webhooks can be delivered multiple times. Process each event only once:

// Save webhook ID
const webhookId = $json.id;

// Check if already processed
const existing = await db.get('processed_webhooks', webhookId);
if (existing) {
  return { status: 'already_processed' };
}

// Process
await processWebhook($json);

// Mark as processed
await db.set('processed_webhooks', webhookId, {
  processedAt: new Date().toISOString()
});

Async Processing

Respond quickly, process later:

app.post('/webhook', async (req, res) => {
  // Respond immediately
  res.status(200).json({ received: true });

  // Process async (after response)
  setImmediate(async () => {
    try {
      await processWebhook(req.body);
    } catch (error) {
      console.error('Webhook processing failed:', error);
      await alertTeam(error);
    }
  });
});

Creating Webhook Documentation

If you offer your own webhooks, document them:

## Order Created Webhook

### Trigger
Fired when a new order is created.

### HTTP Request
- Method: POST
- Content-Type: application/json

### Headers
| Header | Description |
|--------|-------------|
| X-Webhook-Signature | HMAC-SHA256 signature |
| X-Webhook-Timestamp | Unix timestamp |

### Payload
json

{

"event": "order.created",

"data": {

"orderId": "string",

"customerEmail": "string",

"total": "number"

}

}

### Response
Respond with HTTP 200 within 30 seconds.

Costs

SolutionCost
n8n CloudFrom $20/month
Make.comFrom $9/month
Own ServerFrom $5/month (VPS)
Serverless (AWS Lambda)Pay-per-use

Conclusion

Webhooks are fundamental to modern automation:

  • Real-time processing instead of polling
  • Resource-efficient
  • Easy to implement with n8n/Make.com

Next Steps

  1. Create webhook endpoint (n8n recommended)
  2. Register with a service (e.g., Shopify, Stripe)
  3. Implement signature verification
  4. Add error handling
  5. Go live

We can help you with webhook integration, from setup to production.