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:
Webhooks are more efficient because you only receive data when there's something to receive.
How Webhooks Work¶
- Subscribe - You register your endpoint URL with the service
- Event occurs - Something happens (new order, payment, form submission)
- Notification sent - The service POSTs data to your URL
- 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:
- Receive the POST request
- Validate the request (signature, source)
- Process the data
- 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:
- HMAC signature - Hash of payload with shared secret
- Timestamp + signature - Prevents replay attacks
- 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:
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:
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