Idempotency
Make intent-creation requests safe to retry. POST /v2/intents/onramp and /v2/intents/offramp accept an idempotency-key header — mint one UUID per user intent and reuse it across retries. Burn approval uses txSignature as the dedup key instead.
Network failures, timeouts, and user double-clicks are facts of life.
Nora's intent-creation endpoints are designed so you can safely retry
the same call without creating duplicate resources. The mechanism is
the idempotency-key header — a UUID you mint and attach on every
retry of the same logical operation.
How it works
Nora caches the response body against the (idempotency-key, endpoint)
pair within a dedup window. A second call with the same key does not
re-execute the operation — it replays the stored response.
Which operations carry a key
The idempotency-key header is documented in the /v2 OpenAPI
spec on these endpoints:
POST /v2/intents/onramp— mint a fresh UUID per user intent; reuse the same UUID when retrying the same click.POST /v2/intents/offramp— same rule as onramp.
The header is format-validated as a UUID. Sending one is recommended any time you surface a "submit" button to an end-user.
POST /v2/intents/:id/approve-burn— no separateidempotency-keyheader. The on-chaintxSignaturein the body IS the deduplication key. Resubmitting the same signature is a no-op; a different signature against the same intent is rejected.
POST /v2/parties and PATCH /v2/parties/:id/wallet do not document
an idempotency-key header in the current spec — treat retries of
those calls as you would any regular POST/PATCH (the server will
reject a duplicate cpf on POST /v2/parties with 409, and
PATCH /v2/parties/:id/wallet replaces the binding in place, so a
duplicate call is a no-op as long as the body is the same).
GET endpoints are already idempotent and don't need the header.
Rule of thumb: one key per user intent
- User clicks "submit" → generate a fresh UUID → attach it to the first POST and every retry of that same POST.
- User edits the form and clicks submit again → generate a new UUID. Reusing the old one would replay the stale response.
- Your app crashes between "submit" and the response → on restart, reuse the persisted UUID. The backend will either return the original success (if the first call landed) or execute freshly (if it didn't).
Burn-approval nuance
POST /v2/intents/:id/approve-burn — the txSignature you submit
IS the dedup key. The backend rejects a second call with a different
txSignature against an intent that already has one. A retry means
resubmitting the same signed bytes, not re-signing. If the blockhash
has expired, you rebuild from a fresh payload (which produces a new
txSignature) and that new signature is what the backend keys on.
Gotchas
- Keys are scoped per endpoint. The same UUID sent to two different paths is two separate cache entries — you don't need to globally coordinate keys.
- Keys are scoped per key (and therefore per instance). Because
X-API-Keybinds to an (org, instance) tuple, the same UUID reused against sandbox and production is two separate operations on two separate servers' cache entries. - Don't generate the key at module load. If your process is long-
lived, a module-level
randomUUID()would make every user intent share one key — the opposite of what you want. Generate inside the request handler, per user action. - TTL is finite. The cache entry expires after a server-side window. A retry that arrives that late will execute as a fresh operation.
See also
- Authentication — how
X-API-Keyscopes your idempotency-key cache - Parties — the party-creation write path
- Error handling — duplicate-key replays return the original response, including the original status code
- Burn signing — where
txSignaturereplacesidempotency-key