Retry Policy
When a webhook delivery fails, Invoice Maker automatically retries with exponential backoff to give your server time to recover.
What counts as a failure
A delivery is considered failed if:
- Your endpoint returns a non-2xx HTTP status code (e.g., 400, 500)
- Your endpoint doesn't respond within 10 seconds
- The connection can't be established (DNS failure, connection refused, TLS error)
A delivery is considered successful if your endpoint returns any 2xx status code (200, 201, 202, 204, etc.).
Retry schedule
Failed deliveries are retried up to 5 times with exponential backoff:
| Attempt | Delay after failure | Cumulative time |
|---|---|---|
| 1st retry | 1 minute | 1 minute |
| 2nd retry | 5 minutes | 6 minutes |
| 3rd retry | 30 minutes | 36 minutes |
| 4th retry | 2 hours | ~2.5 hours |
| 5th retry | 24 hours | ~26.5 hours |
After 5 failed retries, the delivery is marked as permanently failed and no further attempts are made.
Each retry attempt gets a new X-Webhook-ID header, but the event id in the payload body and X-Webhook-Event-ID header stay the same. Use the event ID for deduplication.
Auto-disable
If an endpoint accumulates 100 consecutive failed deliveries (across any number of events), it is automatically disabled. This prevents wasting resources on endpoints that are consistently unreachable.
When an endpoint is auto-disabled:
- No new events are sent to it
- Events that occur while disabled are not queued — they are dropped
- You'll see a "Disabled" badge in your webhook settings
- Re-enable it manually from the dashboard after fixing the issue
Delivery logs
Each webhook endpoint shows a log of recent delivery attempts. For each attempt you can see:
| Field | Description |
|---|---|
| Status | Success or Failed |
| HTTP code | The response status code from your server |
| Duration | How long the request took (ms) |
| Timestamp | When the attempt was made |
| Attempt | Which attempt number (1 = original, 2-6 = retries) |
Best practices
Respond quickly
Return a 200 OK as soon as you've received and validated the event. Do heavy processing asynchronously (e.g., in a background job queue).
app.post('/webhooks', async (req, res) => {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
await queue.add('process-webhook', {
event: req.body
});
});
Make handlers idempotent
Since the same event may be delivered multiple times (due to retries or network issues), your handler should produce the same result whether it runs once or multiple times.
async function handleInvoicePaid(event) {
const eventId = event.id;
// Check if already processed
const existing = await db.query(
'SELECT id FROM processed_events WHERE event_id = $1',
[eventId]
);
if (existing.rows.length > 0) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process the event
await updatePaymentStatus(event.data.invoice.id, 'paid');
// Record that we processed it
await db.query(
'INSERT INTO processed_events (event_id, processed_at) VALUES ($1, NOW())',
[eventId]
);
}
Return meaningful status codes
| Your response | What happens |
|---|---|
200 - 299 | Delivery marked as successful |
400 - 499 | Delivery marked as failed, will be retried |
500 - 599 | Delivery marked as failed, will be retried |
| Timeout (>10s) | Delivery marked as failed, will be retried |
If you receive an event type you don't handle, return 200 anyway. Returning 400 triggers unnecessary retries.
app.post('/webhooks', (req, res) => {
switch (req.body.type) {
case 'invoice.paid':
handleInvoicePaid(req.body);
break;
default:
// Unknown event type — acknowledge it, don't reject it
console.log(`Unhandled event type: ${req.body.type}`);
}
res.status(200).send('OK');
});
Monitor your endpoint
- Check delivery logs regularly for failed deliveries
- Set up alerting if your webhook endpoint goes down
- Watch for the auto-disable threshold (100 consecutive failures)
Plan limits
| Feature | Basic | Pro |
|---|---|---|
| Retry attempts | 5 | 5 |
| Delivery timeout | 10s | 10s |
| Deliveries per hour | 100 | 5,000 |
| Auto-disable threshold | 100 failures | 100 failures |