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 errors
  • 5xx - 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.