Webhook Guide

How Webhooks Work

  1. You register your endpoint URL with the service
  2. An event occurs in the service (e.g., payment succeeded)
  3. Service sends HTTP POST to your endpoint
  4. Your server processes the payload and returns 2xx
  5. If not 2xx, service retries (exponential backoff)

HMAC Signature Verification

// Node.js — verify Stripe-style webhook signature
const crypto = require('crypto');

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

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from('sha256=' + expected)
  );
}

// Express handler
app.post('/webhook', express.raw({type:'application/json'}), (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // Process event...
  res.json({ received: true });
});

Idempotency — Handle Duplicate Events

// Store processed event IDs to prevent duplicate processing
async function processWebhook(eventId, payload) {
  const key = `webhook:${eventId}`;
  const processed = await redis.get(key);

  if (processed) {
    return { status: 'already_processed' };
  }

  // Process the webhook
  await handleEvent(payload);

  // Mark as processed (expire after 24h)
  await redis.setex(key, 86400, '1');
  return { status: 'processed' };
}

Retry Strategy

AttemptDelayTotal Time
1Immediate0s
25s5s
330s35s
45min~5.5min
530min~36min
62h~2.6h
724h~26.6h