Idempotency
A notification you send twice is worse than one you send late. Send an
Idempotency-Key and Nerve guarantees that a replay returns the original
response instead of sending again.
Without a key, every call sends
This is the default. If your HTTP client times out after the gateway has already published the event, your retry produces a second notification. There is no server-side deduplication on payload content.
With a key
curl -X POST https://api.nervly.io/v1/events/trigger \
-H "Authorization: Bearer $NERVE_API_KEY" \
-H "Idempotency-Key: invoice-4471-paid" \
-H "Content-Type: application/json" \
-d '{ "name": "payment-received", "to": { "subscriberId": "user_8f21c", "phone": "+2348012345678" } }'
| Call | Status | Body |
|---|---|---|
| First | 202 Accepted | fresh eventId, status: "QUEUED" |
| Replay with same key | 200 OK | the original body, byte for byte |
The status code is how you tell them apart. The replayed body still says
"status": "QUEUED" and still carries the original eventId and
timestamp — that is the point. Your code sees one event, not two.
Choosing a key
Derive the key from your own domain, so it is the same across every retry of the same logical action:
// Good — stable across retries, unique per real event
const key = `invoice-${invoice.id}-paid`;
const key = `order-${order.id}-shipped-attempt-${order.shipmentCount}`;
// Useless — a new key every attempt defeats the whole mechanism
const key = crypto.randomUUID();
If you cannot derive one, generate the UUID once, store it with the job, and reuse it on every retry of that job.
The retention window
Keys are held in Redis with a TTL set by the gateway's
IDEMPOTENCY_TTL_SECONDS, 24 hours by default.
info/api_schema_contract.md documents a 48-hour window. The gateway's
compiled default is 24 hours (IDEMPOTENCY_TTL_SECONDS=86400). The running
configuration wins — confirm the deployed value before relying on a window
longer than 24 hours.
After the TTL expires, the key is forgotten and a request using it is treated as new. Size your retry windows inside the TTL.
What happens when Redis is unavailable
Idempotency degrades open, not closed. A Redis failure during the lookup is indistinguishable from a cache miss: the request proceeds and the notification is sent. A Redis failure during the write is swallowed, so the key is not recorded and a later replay will send again.
This is a deliberate trade-off — a Redis outage degrades duplicate protection rather than taking ingestion down. It also means idempotency is not a substitute for your own deduplication when duplicates are genuinely unacceptable. For money-movement notifications, keep a sent-flag in your own database.
Conflicts
The error contract defines 409 IDEMPOTENCY_CONFLICT for a key reused with a
different payload. The gateway does not currently compare payloads — a reused
key returns the cached response regardless of what body you send. So reusing a
key for a genuinely different notification silently drops the second one
rather than erroring.
Keep keys unique per logical notification and this cannot bite you.
Next
- Rate limits — the other reason a retry can fail
- Errors — what is safe to retry
- Trigger a notification