Fundamentals

Idempotency

Safely retry mutations using the Idempotency-Key header

Send an Idempotency-Key on every mutation so RevKeen can safely replay a stored response when your request times out or your client retries.

Every POST, PATCH, and PUT that moves money or creates a resource must set Idempotency-Key. Network retries without a key can produce duplicate charges, invoices, or refunds.

The header

POST /v2/invoices HTTP/1.1
Host: api.revkeen.com
x-api-key: rk_live_...
Idempotency-Key: 3d4e1b2c-1f5a-4c9b-9e0e-5a1c8a5a2f7a
Content-Type: application/json
RuleDetail
Accepted methodsPOST, PUT, PATCH and DELETE. GET ignores the header.
Recommended valueUUID v4 (36 chars).
Accepted valueA UUID, or 8–64 characters from A–Z a–z 0–9 _ -. Anything else is rejected with 400.
ScopePer merchant and per API key. Two keys never share a stored response.
Lifetime24 hours from first use.
Expiry behaviourAfter expiry the key is forgotten; reusing it starts a new mutation.

The header is honoured on these resources: charges, payments, refunds, voids, credit notes, subscriptions, subscription schedules, invoices, orders, checkout sessions, cart sessions, customers, products, prices, discounts, product collections, payment links, payment intents, setup intents, usage events, terminal payments, exports, imports, mandates, Direct Debit and webhook endpoints. Requests to other resources ignore it today — make those creates safe by checking for an existing record before retrying.

Webhook endpoint secrets are never stored. POST /v2/webhook-endpoints and POST /v2/webhook-endpoints/{id}/rotate-secret return the signing secret once, in the first response. RevKeen does not keep that secret for the 24 hours it keeps the rest of the response. A replay of the same request (same key and body) returns the endpoint with secret set to a fixed note, not a signing secret, and the header X-Idempotent-Replayed: true. If you did not receive the first response, call rotate-secret with a new key to get a new secret. If a response could not be stored without its secret, a replay is refused with 409 and code: "IDEMPOTENCY_KEY_NOT_REPLAYABLE" and the request does not run again.

Replay semantics

When RevKeen receives a request with an Idempotency-Key it has seen before in the last 24 hours:

  1. Same key + same method, path and body → return the stored response (status and body). The underlying resource is not mutated again, and the response carries X-Idempotent-Replayed: true.
  2. Same key + a different body, method or path → 422 with code: "IDEMPOTENCY_KEY_MISMATCH". The original stored response is not returned. Reusing a key on a different endpoint counts as a mismatch.
  3. Same key while the first request is still running → 409 with code: "IDEMPOTENCY_KEY_IN_PROGRESS". Back off briefly and retry with the same key.

Idempotency errors use a flat body, not the nested error envelope described in Errors:

{
  "error": "Idempotency-Key has already been used with a different request payload",
  "code": "IDEMPOTENCY_KEY_MISMATCH",
  "originalRequestId": "6f1c..."
}

Branch on the top-level code. The full list is in Idempotency errors.

Which responses are stored

ScenarioStored?Retry behaviour
2xx successYesRetry returns the stored success response.
401, 403 or 429NoThe request never ran. Fix the cause, then retry with the same key.
Other 4xxYesRetry returns the same 4xx.
2xx from a webhook endpoint create or secret rotationWithout the secretRetry returns the stored response with secret replaced by a note.
5xx on a money-moving resource (charges, payments, refunds, voids, payment intents, terminal payments, mandates, Direct Debit)YesRetry returns the stored 5xx and never moves money twice. Investigate before retrying with a new key.
5xx on any other resourceNoRetry with the same key re-runs the request.
Network timeout (no response received)NoRetry with the same key: it either replays or runs once.

Idempotency errors

StatuscodeMeaning
400INVALID_IDEMPOTENCY_KEYThe key is not a UUID or 8–64 allowed characters.
400IDEMPOTENCY_KEY_REQUIREDThis endpoint requires a key and none was sent.
409IDEMPOTENCY_KEY_IN_PROGRESSThe first request with this key is still running.
409IDEMPOTENCY_KEY_NOT_REPLAYABLEThe key was used before an API upgrade, or its response held a one-time secret that could not be stored, so the result cannot be replayed. Retrieve the resource to see what happened; do not retry the mutation with a new key unless you have confirmed it did not run.
422IDEMPOTENCY_KEY_MISMATCHThe key was used with a different method, path or body.
503IDEMPOTENCY_UNAVAILABLEThe idempotency store is unavailable. Retry with the same key.

SDK support

The published SDKs (TypeScript 1.20260822.1541, Go v1.20260819.1529 and PHP 1.20260819.1529) do not add an Idempotency-Key for you, do not retry requests automatically, and have no per-request idempotency option. Send the header yourself:

  • HTTP / cURL -- set the Idempotency-Key header on the request.
  • TypeScript -- pass the header through the client's headers option. The option applies to every request made by that client, so create a client for the mutation you are making.
  • Go and PHP -- the published releases cannot set the header per request. Use HTTP directly for mutations that need an idempotency key. The Go module also has a module-path limitation.

Examples

curl https://staging-api.revkeen.com/v2/refunds \
  -H "x-api-key: $REVKEEN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "payment_id": "pay_01HT...", "amount_minor": 1500 }'

Best practices

  • Generate one key per logical operation, not per HTTP retry. A background job that charges a customer should use the same key across all in-process retries; the next scheduled run uses a fresh key.
  • Persist the key alongside the record you are creating (invoice, charge, refund). Reuse it if the job is re-run.
  • Do not reuse keys across different request bodies or endpoints — that produces a 422 IDEMPOTENCY_KEY_MISMATCH.
  • Expect a stored 5xx on money-moving resources. It is replayed on retry so money never moves twice. Retrieve the resource to see what happened before deciding whether to retry with a new key.

See also

Owner: david · Reviewed 12 Oct 2026 · Next review 6 Nov 2026

On this page