nora

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

Loading diagram…

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-burnno separate idempotency-key header. The on-chain txSignature in 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-Key binds 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-Key scopes 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 txSignature replaces idempotency-key

On this page