Idempotency-Key never executes side effects twice.
How it works
Include anIdempotency-Key header on every request to an endpoint that requires one. Natural records the key alongside the outcome of the request, and a later request with the same key returns that recorded outcome instead of running the operation again.
- Same key + same request + finished -> Replays the recorded response.
- Same key + different request -> Returns
409 conflict. - Same key + original still running -> Returns
409 conflict. - Different key -> Treated as a new mutation attempt.
X-Idempotency-Replayed: true. That header is the only thing distinguishing a replay from a fresh execution, so check it if you need to tell them apart.
Endpoints that require a key
TheIdempotency-Key header is required on the endpoints below. Each one is a mutating operation whose accidental repetition would be visible to you or your customers.
Omitting the header on any of these returns
400 with the error code invalid_value. A key longer than 255 characters is rejected the same way. Endpoints not listed here ignore the header.
Keys
A key must be unique per logical operation and stable across every retry of that operation. Natural binds the key to the meaningful contents of the request it first arrives with, so the same key must always mean the same request.- Use a UUIDv4/UUIDv7, or any unique string of up to 255 characters.
- Generate the key once, when the operation is first attempted, and reuse it for every retry of that attempt, including retries after a timeout.
- Generate a new key only when the user or system starts a genuinely new operation.
- Never derive a key from anything that changes between retries (a timestamp, an attempt counter, a per-request random value). A key that changes per retry provides no protection at all.
- Don’t put sensitive data in keys.
Replay window
A record is created when the request starts, and is finalized once the operation reaches a terminal outcome: success or failure. The record then expires 48 hours after it finished, not 48 hours after the request arrived. Within those 48 hours, the same key replays the recorded outcome. Once they elapse the key is forgotten, and reusing it starts an entirely new request, re-running the side effect. Treat 48 hours as the window in which a retry is guaranteed safe, not as a deduplication guarantee for the life of the key. A record for an operation that is still running does not expire on its own. If an operation never reaches a terminal outcome, its key stays reserved and requests reusing it keep returning409 conflict. Contact support if a key stays conflicted.
Conflicts
Both conflict cases return409 with the error code conflict:
- You retried an identical request. The original is still running. Wait, then retry the same key with exponential backoff.
- You changed the request but reused the key. This is a client bug: a key is bound to the first request sent under it. Use a fresh key for the new operation.
meta.supportId when contacting support about a conflict; the underlying reason is recorded against that ID.
Failures are replayed too
Failures are recorded exactly like successes. When an operation fails terminally, the same key replays that same error (identical code, status, and detail) for the rest of the window. Retrying a recorded failure with the same key does not re-run it. Responses that do not reflect a decision about your request are not recorded:408 (timeout), 429 (rate limited), and a 503 caused by a transient outage of one of Natural’s services. Retrying any of them with the same key genuinely re-runs the operation. A 503 that is a decision, such as a balance check that could not be completed, is recorded like any other error. One 400 is also not recorded: wire.beneficiary_address_required on POST /wire, so after you add the address the same key re-runs and succeeds.
When to retry
Related
- Error Handling: Handling conflicts and other errors