Idempotency & reliability
Three promises, and what backs each one:
1. Nothing is ever lost
Every business event is written to a durable event store in the same
database transaction as the case change — an update cannot exist without its
event. Webhook delivery retries on a ladder (0s → 30s → 2m → 10m → 1h → 6h → 24h); after that the event parks replayable for 30 days (self-serve).
The poll API is a second, independent path to
the same events.
2. Nothing is applied twice
| Boundary | Mechanism |
|---|---|
| Your batch pushes | Content-hash dedupe: an identical committed batch is a guaranteed no-op. Optionally send an Idempotency-Key header — a replay returns the original response verbatim. |
| Your payment confirms (Mode B) | Unique on (loan, reference) — a replay gets 409 DUPLICATE_CONFIRMATION with the original booking. |
| Our webhooks | At-least-once delivery — you dedupe on eventId (retries and replays are normal, not errors). |
3. Order is recoverable, not guaranteed
Retries make in-order delivery impossible, so every event carries a per-case
monotonic sequence. Receiver rule:
- apply only if
sequence> the last you applied for that loan; - on a gap, fetch
GET /cases/{loan}for the authoritative snapshot; - payloads carry absolute values (
totalCollected,outstandingAfter) — a missed intermediate event can never corrupt your books.
Under concurrent delivery a case.closed (seq 4) can genuinely arrive before
the payment.collected (seq 2–3) that caused it. A sequence-aware receiver
handles this without special cases — the terminal event's absolute totals are
already correct.