Webhook Guide
How Webhooks Work
- You register your endpoint URL with the service
- An event occurs in the service (e.g., payment succeeded)
- Service sends HTTP POST to your endpoint
- Your server processes the payload and returns 2xx
- 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
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 5s | 5s |
| 3 | 30s | 35s |
| 4 | 5min | ~5.5min |
| 5 | 30min | ~36min |
| 6 | 2h | ~2.6h |
| 7 | 24h | ~26.6h |