Skip to content

Webhook Basics

Webhooks are automated messages sent from one system to another when something happens. This guide covers what they are, how they work, and best practices.


What is a Webhook?

A webhook is an HTTP callback - when an event occurs in System A, it sends data to System B via an HTTP POST request.

Without webhooks (polling):

Your App: "Any new orders?"
Shop: "No"
Your App: "Any new orders?"
Shop: "No"
Your App: "Any new orders?"
Shop: "Yes, here's one!"

With webhooks:

Shop: "New order! Here's the data." → Your App

Webhooks are more efficient because you only receive data when there's something to receive.


How Webhooks Work

  1. Subscribe - You register your endpoint URL with the service
  2. Event occurs - Something happens (new order, payment, form submission)
  3. Notification sent - The service POSTs data to your URL
  4. You respond - Your endpoint processes the data and returns 200 OK

Example Flow

1. You tell Stripe: "Send payment events to https://myapp.com/webhooks/stripe"

2. Customer makes a payment

3. Stripe sends POST request:
   POST https://myapp.com/webhooks/stripe
   {
     "type": "payment_intent.succeeded",
     "data": {
       "object": {
         "amount": 2000,
         "currency": "usd"
       }
     }
   }

4. Your server processes and returns:
   HTTP 200 OK

Receiving Webhooks

Basic Endpoint Structure

Your webhook endpoint should:

  1. Receive the POST request
  2. Validate the request (signature, source)
  3. Process the data
  4. Return 200 OK quickly

Example (Node.js/Express)

app.post('/webhooks/myservice', (req, res) => {
  // 1. Verify the webhook signature
  const signature = req.headers['x-signature'];
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Acknowledge receipt immediately
  res.status(200).send('OK');

  // 3. Process asynchronously
  processWebhook(req.body);
});

Key Points

  • Return 200 quickly - Don't do heavy processing before responding
  • Process asynchronously - Queue the work for background processing
  • Be idempotent - You might receive the same webhook multiple times

Webhook Security

Signature Verification

Most services sign webhooks so you can verify they're genuine.

Common methods:

  1. HMAC signature - Hash of payload with shared secret
  2. Timestamp + signature - Prevents replay attacks
  3. API key in header - Simple but less secure

Example HMAC verification:

const crypto = require('crypto');

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

  return signature === expected;
}

Security Checklist

  • Always use HTTPS endpoints
  • Verify webhook signatures
  • Check timestamps to prevent replay attacks
  • Whitelist source IPs if available
  • Don't trust webhook data blindly - validate it

Common Challenges

1. Duplicate Deliveries

Webhooks may be sent multiple times. Handle this with idempotency.

Solution:

// Store processed webhook IDs
const processedIds = new Set();

function handleWebhook(webhook) {
  if (processedIds.has(webhook.id)) {
    return; // Already processed
  }

  processedIds.add(webhook.id);
  // Process the webhook...
}

2. Out-of-Order Delivery

Events might arrive in a different order than they occurred.

Solution: - Include timestamps in your logic - Use event sequence numbers if available - Design your system to handle any order

3. Endpoint Downtime

If your endpoint is down, you'll miss webhooks.

Solution: - Implement retry logic (most services retry automatically) - Provide a way to replay missed webhooks - Monitor your endpoint uptime

4. Slow Processing

If your endpoint is slow, the sender may time out and retry.

Solution: - Return 200 immediately - Process heavy work asynchronously - Use a message queue for reliability


Testing Webhooks

During Development

ngrok - Expose your local server to the internet:

ngrok http 3000
# Gives you: https://abc123.ngrok.io
# Use this as your webhook URL

Webhook.site - Inspect incoming webhooks: 1. Go to webhook.site 2. Get a unique URL 3. Use it as your webhook endpoint 4. See all incoming requests

Replaying Webhooks

Most services let you resend webhooks from their dashboard:

  • Stripe: Dashboard → Developers → Webhooks → Event → Resend
  • GitHub: Settings → Webhooks → Recent Deliveries → Redeliver

Best Practices

1. Respond Quickly

// Good - respond immediately, process later
app.post('/webhook', (req, res) => {
  res.status(200).send('OK');
  queue.add(req.body); // Process async
});

// Bad - process before responding
app.post('/webhook', async (req, res) => {
  await heavyProcessing(req.body); // Might timeout
  res.status(200).send('OK');
});

2. Log Everything

app.post('/webhook', (req, res) => {
  console.log('Webhook received:', {
    headers: req.headers,
    body: req.body,
    timestamp: new Date().toISOString()
  });
  // ...
});

3. Handle Failures Gracefully

app.post('/webhook', async (req, res) => {
  try {
    await processWebhook(req.body);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing failed:', error);
    // Return 500 so the service retries
    res.status(500).send('Processing failed');
  }
});

4. Use a Queue

For reliability, put webhooks in a queue:

Webhook → Your Endpoint → Queue → Worker → Database
            200 OK (immediate)

Common Webhook Providers

Service Documentation
Stripe stripe.com/docs/webhooks
GitHub docs.github.com/webhooks
Shopify shopify.dev/docs/apps/webhooks
Twilio twilio.com/docs/usage/webhooks
Slack api.slack.com/messaging/webhooks

Troubleshooting

"Webhook not received"

  • Check endpoint URL is correct and accessible
  • Verify HTTPS certificate is valid
  • Check firewall/security rules
  • Look at the service's webhook logs for delivery attempts

"Signature verification failed"

  • Ensure you're using the raw request body (not parsed JSON)
  • Check you're using the correct secret
  • Verify the signature algorithm matches

"Webhook received but data not processed"

  • Check your application logs for errors
  • Verify you're handling the correct event type
  • Ensure your processing logic completed successfully