Fundamentals

Creating Webhooks: The Complete Guide for Beginners (with n8n and Make.com)

What are webhooks and how do you create them? Simple guide with examples.

13 min read

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.

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

|

  • Event occurs (e.g., new order)
  • |

  • Service sends POST request to your URL
  • |

  • Your server receives the data
  • |

  • You process the data
  • |

  • 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
  • Create new workflow
  • Add "Webhook" node
  • 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
  • New scenario
  • Trigger: "Webhooks" -> "Custom webhook"
  • Click "Add" -> Give it a name
  • 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):
  • Settings -> Notifications -> Webhooks
  • "Create webhook" -> Event: "Order creation"
  • URL: Your n8n/Make.com URL
  • 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

    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:
  • Repository -> Settings -> Webhooks -> Add webhook
  • URL: Your webhook URL
  • Content type: application/json
  • 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:
  • Typeform -> Connect -> Webhooks
  • Enter URL
  • 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
  • Open https://webhook.site
  • Copy unique URL
  • Use as webhook endpoint
  • All requests are displayed
  • n8n Debug

  • Open workflow
  • Activate "Test Workflow"
  • Send webhook
  • 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:

    <h2 class="text-2xl font-bold mt-10 mb-6 text-gray-900">Order Created Webhook</h2>
    
    

    <h3 class="text-xl font-bold mt-8 mb-4 text-gray-900">Trigger</h3>

    Fired when a new order is created.

    <h3 class="text-xl font-bold mt-8 mb-4 text-gray-900">HTTP Request</h3>

    • Method: POST
    • Content-Type: application/json

    <h3 class="text-xl font-bold mt-8 mb-4 text-gray-900">Headers</h3>

    HeaderDescription
    X-Webhook-SignatureHMAC-SHA256 signature
    X-Webhook-TimestampUnix timestamp

    <h3 class="text-xl font-bold mt-8 mb-4 text-gray-900">Payload</h3>

    json

    {

    "event": "order.created",

    "data": {

    "orderId": "string",

    "customerEmail": "string",

    "total": "number"

    }

    }

    <h3 class="text-xl font-bold mt-8 mb-4 text-gray-900">Response</h3>
    

    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

  • Create webhook endpoint (n8n recommended)
  • Register with a service (e.g., Shopify, Stripe)
  • Implement signature verification
  • Add error handling
  • Go live
  • We can help you with webhook integration, from setup to production.

    Questions About Automation?

    Our experts will help you make the right decisions for your business.