Skip to main content

Idempotency

Nexa is designed to tolerate retries at every layer. This page lists the idempotency scopes and the keys that make each one safe.

Why it matters

Distributed systems retry. Airline feeds replay. Operators double-click. Background jobs re-enqueue. Without idempotency you end up with duplicate cases, double bookings, and double emails — every one of which is visible to passengers and expensive to unwind.

Nexa treats idempotency as the default, not an opt-in.

Scopes

ScopeKeyPrevents
Case creationexternalEventIdDuplicate cases from retried airline webhooks
Demand request(caseUrn, type, requestedAtBucket)Double-approved surges
Allocation wave(caseUrn, demandRequestUrn, waveSeq)Re-running the same wave during a worker retry
Booking(caseUrn, waveUrn, groupId)Double bookings for the same group
Notification(groupId, type, channel, reservationUrn)Duplicate vouchers
Payment tokenization(caseUrn, reservationUrn)Double card charges
Agent run(itemUrn, runSeq)Duplicate AI Model invocations on the same review item

Every scope is TTL-indexed so keys don't grow unbounded. The default TTL is 7 days, which is long enough for every expected retry path.

How keys are derived

Keys come from upstream, not from request IDs:

  • externalEventId is the airline's ID. The airline is the source of truth for "did we already submit this?" — Nexa respects it.
  • (caseUrn, waveUrn, groupId) is derived from platform state, not from the worker's job ID.
  • (groupId, type, channel, reservationUrn) is derived from the thing being notified about, not from the send request.

This matters because a worker retry uses the same key, but a new operator action (resend, re-book) uses a different key — so retries are deduped while genuine new actions go through.

Behavior on duplicate

  • POST /cases with an existing externalEventId returns 200 with the existing case URN and a X-Nexa-Replayed: true header. No new case is created.
  • Booking worker sees an existing CONFIRMED reservation for (caseUrn, waveUrn, groupId) and returns it. No provider call is made.
  • Notification worker with an existing SENT record skips the send and returns the existing record.

The replayed response is byte-identical to the original where possible, so downstream systems can't tell (and don't need to).

Operator-initiated duplication

Sometimes operators want a "duplicate":

  • Resend a voucher — the operator wants a second email sent because the passenger never got the first.
  • Rebook a cancelled reservation at a new hotel.

Both of these are new logical actions, not retries. They have their own endpoints with their own keys (resendSeq, rebookSeq) so they coexist with the idempotent retry behaviour.

Diagnostics

curl "https://us-central1.api.nexastudio.io/admin/idempotency/lookup?scope=BOOKING&caseUrn=…&waveUrn=…&groupId=…"

Returns whether the key exists, when it was first seen, and the URN of the result it's protecting. Useful when debugging "why didn't this send a second time?"

Was this helpful?