Retry Policy & Best Practices
Retry Policy
If your endpoint returns a non-2xx status code, we'll retry with exponential backoff:
| Attempt | Delay | Approximate Time |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 1 minute | 1 min |
| 3 | 5 minutes | 6 min |
| 4 | 30 minutes | 36 min |
| 5 | 2 hours | 2.5 hrs |
| 6 (final) | 24 hours | ~27 hrs |
After 6 failed attempts, the delivery is moved to the Dead Letter Queue where you can manually retry it from the dashboard.
Successful Response
Your endpoint should return a 2xx status code to acknowledge receipt:
{
"received": true
}
Any response body is ignored - only the status code matters.
Failed Responses
These status codes will trigger a retry:
4xx(except 410) - Client errors5xx- Server errors- Timeouts (> 30 seconds)
- Connection errors
A 410 Gone status code will not retry and will disable the webhook endpoint.
Best Practices
1. Return 200 Quickly
Acknowledge receipt immediately and process the event asynchronously:
app.post('/webhooks/esca', async (req, res) => {
// Verify signature first
verifyWebhookSignature(req.body, req.headers['x-esca-webhook-signature'], secret);
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(req.body).catch(console.error);
});
2. Handle Duplicates (Idempotency)
Use the id field to prevent processing the same event twice:
async function processWebhook(event) {
// Check if already processed
const existing = await db.webhookEvents.findOne({ eventId: event.id });
if (existing) {
console.log(`Event ${event.id} already processed, skipping`);
return;
}
// Process the event
await handleEvent(event);
// Mark as processed
await db.webhookEvents.create({ eventId: event.id, processedAt: new Date() });
}
3. Verify Signatures
Always verify webhook signatures in production to ensure requests are from Esca:
if (process.env.NODE_ENV === 'production') {
verifyWebhookSignature(payload, signature, secret);
}
4. Use HTTPS
Webhook URLs must use HTTPS in production. This ensures:
- Data is encrypted in transit
- Your endpoint is authenticated via SSL certificate
- Man-in-the-middle attacks are prevented
5. Monitor Failures
Set up alerts for failed webhook deliveries:
- Monitor your endpoint's error rate
- Check the Dead Letter Queue regularly
- Set up notifications for repeated failures
6. Log Everything
Keep detailed logs for debugging:
app.post('/webhooks/esca', (req, res) => {
console.log({
timestamp: new Date().toISOString(),
eventId: req.body.id,
eventType: req.body.type,
headers: {
signature: req.headers['x-esca-webhook-signature'],
timestamp: req.headers['x-esca-webhook-timestamp'],
},
});
// Process webhook...
});
Testing Webhooks
Local Development
Use a tunneling service like ngrok to expose your local server:
ngrok http 3000
Then use the generated URL (e.g., https://abc123.ngrok.io/webhooks/esca) as your webhook endpoint.
Sandbox Environment
In sandbox mode, you can trigger test webhooks from your Developer Dashboard. Navigate to the Webhooks section and use the "Send Test Webhook" feature to send sample webhook payloads to your configured endpoint.
You can also use the Simulate Deposit endpoint to trigger a real virtual_account.credited webhook flow with actual balance updates.