When a delivery attempt fails (non-2xx response or timeout), Arowana retries with exponential backoff. Your handler must be idempotent and respond quickly.
Retry schedule
Failed deliveries are retried up to 5 times over ~24 hours:
| Attempt | Delay after failure |
|---|
| 1 | Immediate |
| 2 | ~1 minute |
| 3 | ~5 minutes |
| 4 | ~1 hour |
| 5 | ~24 hours |
After 5 failed attempts, the event is marked as permanently failed. You can view failed deliveries in the dashboard and replay them manually.
Ordering
Events are delivered best-effort ordered. Network conditions, retries, and parallel delivery mean events may arrive out of order. Always use the id and timestamp fields on the event envelope for deduplication and sequencing — not arrival order.
Timeout
Your handler must respond with a 2xx status code within 5 seconds. If it takes longer, the delivery is treated as failed and retried.
Process events asynchronously — never block on database writes, API calls, or downstream work inside the handler. Accept the event, return 200, and process it in a background job.
Idempotency
The same event id may arrive more than once due to retries or replays. Your handler must be idempotent:
- Store processed event IDs and skip duplicates
- Use upsert operations rather than inserts
- Design side-effects to be safe to repeat
app.post("/webhooks/arowana", async (req, res) => {
const event = JSON.parse(req.body);
const alreadyProcessed = await db.webhookEvents.findById(event.id);
if (alreadyProcessed) {
return res.status(200).send("Already processed");
}
await db.webhookEvents.insert({ id: event.id, processed_at: new Date() });
await processEvent(event);
res.status(200).send("OK");
});