Webhook Retry Logic
The payment gateway implements automatic retry logic for failed webhook deliveries with exponential backoff.
Retry Schedule
Webhooks are retried up to 7 times with increasing delays:
| Attempt | Delay | Time After First Attempt |
|---|---|---|
| 1 | Immediate | 0 seconds |
| 2 | 1 minute | 1 minute |
| 3 | 10 minutes | 11 minutes |
| 4 | 1 hour | 1 hour 11 minutes |
| 5 | 3 hours | 4 hours 11 minutes |
| 6 | 12 hours | 16 hours 11 minutes |
| 7 | 24 hours | 40 hours 11 minutes |
After 7 failed attempts over approximately 40 hours, the webhook is marked as failed.
Failure Conditions
Webhooks are retried when:
- HTTP Status: Any non-2xx status code
- Timeout: Response not received within 10 seconds
- Connection Error: Unable to establish connection
Success Conditions
A webhook is considered successful when:
- HTTP status code 200-299 is returned
- Response received within the 10-second timeout
Handling Retries
Idempotent Processing
Since the same event may be delivered multiple times, always process events idempotently:
async function processWebhook(event) {
const paymentId = event.payment.id;
const status = event.payment.status;
// Check if already processed
const existing = await db.webhookEvents.findOne({
paymentId,
status
});
if (existing) {
console.log(`Event already processed: ${paymentId}:${status}`);
return { status: 'already_processed' };
}
// Process event
await handlePaymentEvent(event);
// Mark as processed
await db.webhookEvents.insert({
paymentId,
status,
processedAt: new Date()
});
return { status: 'success' };
}
Quick Response
Return a 200 response as quickly as possible. If your processing takes time, handle it asynchronously:
app.post('/webhooks/payments', (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body).catch((error) => {
console.error('Webhook processing error:', error);
});
});
Permanent Errors
If the error is permanent (invalid data, business logic rejection), return 200 to stop retries and log the issue:
async function handleWebhook(event) {
try {
await processEvent(event);
return { status: 'success' };
} catch (error) {
if (isPermanentError(error)) {
console.error('Permanent webhook error, stopping retries:', error);
await logPermanentFailure(event, error);
return { status: 'permanent_failure' }; // Still return 200
}
throw error; // Temporary error — trigger retry
}
}
Best Practices
- Implement idempotency — process events only once
- Return 200 quickly — process asynchronously if needed
- Log all attempts — for debugging retry issues
- Handle permanent failures — return 200 to stop retries for invalid data
- Monitor failure rates — set up alerts for repeated failures