Best Practices
Follow these guidelines for reliable webhook handling.
Respond Quickly
Return a 200 response within 10 seconds. Process the webhook asynchronously.
app.post('/webhooks/kula', (req, res) => {
// Verify signature
if (!verifyWebhook(req)) {
return res.status(401).send('Invalid signature');
}
// Respond immediately
res.status(200).send('OK');
// Queue for async processing
webhookQueue.add(req.body);
});
Handle Idempotency
Use event_id to detect and skip duplicate deliveries.
async function processWebhook(event) {
const processed = await db.webhookEvents.findOne({
eventId: event.event_id
});
if (processed) {
return; // Already handled
}
await handleEvent(event);
await db.webhookEvents.insert({
eventId: event.event_id,
processedAt: new Date()
});
}
Don't Rely Solely on Webhooks
Webhooks can fail or be delayed. Periodically reconcile with the API.
// Daily reconciliation job
async function reconcile() {
const apiData = await fetchFromApi();
const localData = await fetchFromDatabase();
const missing = findMissing(apiData, localData);
await processMissing(missing);
}
Handle Out-of-Order Delivery
Events may arrive in a different order than they occurred. Use timestamps to handle this.
async function handleApplicationUpdate(event) {
const existing = await db.applications.findOne({
id: event.data.id
});
// Only update if this event is newer
if (existing && existing.updatedAt > event.created_at) {
return;
}
await db.applications.update(event.data);
}
Store Raw Payloads
Save the original payload for debugging.
async function processWebhook(event) {
// Store raw payload first
await db.webhookPayloads.insert({
eventId: event.event_id,
payload: JSON.stringify(event),
receivedAt: new Date()
});
// Then process
await handleEvent(event);
}
Implement Circuit Breaker
Prevent cascading failures when your processing is slow.
const circuitBreaker = {
failures: 0,
lastFailure: null,
threshold: 5,
resetTime: 60000,
isOpen() {
if (this.failures < this.threshold) return false;
if (Date.now() - this.lastFailure > this.resetTime) {
this.failures = 0;
return false;
}
return true;
},
recordFailure() {
this.failures++;
this.lastFailure = Date.now();
},
recordSuccess() {
this.failures = 0;
}
};
Summary
| Practice | Why |
|---|---|
| Respond quickly | Prevent timeouts and retries |
| Process async | Don't block the response |
| Check event_id | Handle duplicates |
| Store payloads | Debug issues |
| Reconcile periodically | Catch missed events |
| Handle out-of-order | Events may arrive late |
| Use circuit breaker | Prevent cascading failures |