Skip to main content

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

PracticeWhy
Respond quicklyPrevent timeouts and retries
Process asyncDon't block the response
Check event_idHandle duplicates
Store payloadsDebug issues
Reconcile periodicallyCatch missed events
Handle out-of-orderEvents may arrive late
Use circuit breakerPrevent cascading failures