Skip to main content

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" } }'
CallStatusBody
First202 Acceptedfresh eventId, status: "QUEUED"
Replay with same key200 OKthe 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.

The published contract says 48 hours

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.

Idempotency is best-effort, not transactional

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