Webhook Retry Logic
AfriRoute guarantees at-least-once delivery. If your endpoint does not acknowledge an event with a 2xx response, the event is retried on an exponential backoff schedule. This page explains the retry behavior and how to build a handler that stays correct under retries.
✅ What Counts as Success
| Response | Outcome |
|---|---|
2xx | Delivery succeeded — no retry |
3xx | Treated as failure — redirects are not followed |
4xx | Treated as failure and retried (except 410 Gone, which disables retries for that event) |
5xx | Treated as failure and retried |
| Timeout (> 10s) | Treated as failure and retried |
| Connection error | Treated as failure and retried |
🔁 Retry Schedule
AfriRoute makes up to 6 attempts over roughly 24 hours with exponential backoff and jitter:
| Attempt | Approx. delay after previous |
|---|---|
| 1 | immediate |
| 2 | ~30 seconds |
| 3 | ~2 minutes |
| 4 | ~10 minutes |
| 5 | ~1 hour |
| 6 | ~6 hours |
After the final failed attempt the event is marked undelivered and moved to a dead-letter log you can inspect and replay from the dashboard.
🔑 Idempotency Is Required
Because the same event may be delivered more than once (e.g. your 2xx was lost in transit), your handler must be idempotent. Key off the event id.
const processed = new Set(); // use Redis/DB in production
app.post('/webhook', async (req, res) => {
const { id, event, data } = req.body; // after signature verification
if (processed.has(id)) {
return res.sendStatus(200); // already handled — ack and skip
}
try {
await handleEvent(event, data);
processed.add(id);
res.sendStatus(200);
} catch (err) {
console.error(err);
res.sendStatus(500); // signal AfriRoute to retry
}
});
@app.route("/webhook", methods=["POST"])
def webhook():
body = request.json # after signature verification
if seen(body["id"]):
return "", 200
try:
handle_event(body["event"], body["data"])
mark_seen(body["id"])
return "", 200
except Exception:
return "", 500 # triggers retry
🧯 Replaying Undelivered Events
From Dashboard → Webhooks → Delivery Log you can:
- Inspect the request, response status, and body of each attempt.
- Manually replay an individual event or a range after fixing an outage.
- Filter by event type, status, and time window.
You can also list recent deliveries via the API:
curl https://api.afriroute.ai/api/v1/webhooks/wh_5k2j9/deliveries \
-H "Authorization: Bearer $AFRIROUTE_API_KEY"
💡 Best Practices
- Respond within 10 seconds — offload slow work to a queue and ack immediately.
- Persist idempotency keys in a durable store (Redis/DB), not in memory.
- Return
5xxon transient failures so events are retried; return2xxonly when safely handled. - Return
410 Gonefor endpoints you have permanently retired to stop retries cleanly. - Monitor the delivery log and alert when undelivered counts rise.
⚠️ Failure Codes
| Code | Meaning |
|---|---|
WEBHOOK_TIMEOUT | Endpoint did not respond within 10s |
WEBHOOK_UNREACHABLE | DNS/TLS/connection failure |
WEBHOOK_REJECTED | Endpoint returned a non-2xx status |
WEBHOOK_EXHAUSTED | All retry attempts failed; event dead-lettered |
See the error code reference for the full list.
📚 Related Resources
Last Updated: May 2026 | Need help? [email protected]