Retry Policy
When a webhook delivery fails, Otesse automatically retries with exponential backoff. Here is the full retry behavior.
What Counts as a Failure
A delivery is considered failed if:
- Your endpoint does not respond within 30 seconds (timeout)
- Your endpoint returns a non-2xx HTTP status code (3xx, 4xx, 5xx)
- The connection cannot be established (DNS failure, connection refused, SSL error)
Retry Schedule
Failed deliveries are retried with increasing delays:
| Attempt | Delay After Previous Attempt | Total Time Since First Attempt |
|---|---|---|
| 1 (original) | — | 0 |
| 2 | 1 minute | 1 minute |
| 3 | 5 minutes | 6 minutes |
| 4 | 30 minutes | 36 minutes |
| 5 | 2 hours | ~2.5 hours |
| 6 | 6 hours | ~8.5 hours |
| 7 | 12 hours | ~20.5 hours |
| 8 (final) | 24 hours | ~44.5 hours |
After 8 total attempts (including the original), the delivery is marked as permanently failed.
Monitoring Failures
Dashboard
View delivery status in Settings > Integrations > Webhooks > [Endpoint] > Deliveries:
- Green = successful delivery (2xx response)
- Yellow = pending retry
- Red = permanently failed
Failure Alerts
If your endpoint fails repeatedly:
- 5 consecutive failures — Warning email sent to admin
- 100 consecutive failures — Webhook endpoint is automatically disabled
- Disabled endpoint — No more deliveries are attempted until you re-enable it
Handling Failures in Your Application
Return 200 Quickly
Process events asynchronously. Accept the webhook, return 200, and process the event in a background job:
app.post('/webhooks/otesse', async (req, res) => {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid');
}
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
await queue.add('processWebhook', {
event: req.body,
deliveryId: req.headers['x-otesse-delivery'],
});
});
Implement Idempotency
Since retries can deliver the same event multiple times:
async function processWebhook(event, deliveryId) {
// Check if already processed
const existing = await db.webhookDeliveries.findOne({ deliveryId });
if (existing) {
console.log('Duplicate delivery, skipping:', deliveryId);
return;
}
// Process the event
await handleEvent(event);
// Record the delivery
await db.webhookDeliveries.insert({
deliveryId,
eventType: event.type,
processedAt: new Date(),
});
}
Handle Out-of-Order Delivery
Events may arrive out of order during retries. Use the created_at timestamp to determine event sequence:
async function handleBookingUpdate(event) {
const booking = await db.bookings.findOne({ id: event.data.id });
// Only apply if this event is newer than our current data
if (booking && new Date(event.created_at) <= new Date(booking.last_webhook_at)) {
console.log('Stale event, skipping');
return;
}
await db.bookings.update({ id: event.data.id }, {
...event.data,
last_webhook_at: event.created_at,
});
}
Replaying Events
If you need to reprocess events (e.g., after fixing a bug in your handler):
- Go to the webhook delivery history
- Filter by date range and event type
- Click Replay on individual deliveries or Replay All for a filtered set
- Events are re-sent with the same payload but a new delivery ID
On this page