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| Rule | Detail |
|---|---|
| Accepted methods | POST, PUT, PATCH and DELETE. GET ignores the header. |
| Recommended value | UUID v4 (36 chars). |
| Accepted value | A UUID, or 8–64 characters from A–Z a–z 0–9 _ -. Anything else is rejected with 400. |
| Scope | Per merchant and per API key. Two keys never share a stored response. |
| Lifetime | 24 hours from first use. |
| Expiry behaviour | After 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:
- 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. - Same key + a different body, method or path →
422withcode: "IDEMPOTENCY_KEY_MISMATCH". The original stored response is not returned. Reusing a key on a different endpoint counts as a mismatch. - Same key while the first request is still running →
409withcode: "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
| Scenario | Stored? | Retry behaviour |
|---|---|---|
2xx success | Yes | Retry returns the stored success response. |
401, 403 or 429 | No | The request never ran. Fix the cause, then retry with the same key. |
Other 4xx | Yes | Retry returns the same 4xx. |
2xx from a webhook endpoint create or secret rotation | Without the secret | Retry 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) | Yes | Retry returns the stored 5xx and never moves money twice. Investigate before retrying with a new key. |
5xx on any other resource | No | Retry with the same key re-runs the request. |
| Network timeout (no response received) | No | Retry with the same key: it either replays or runs once. |
Idempotency errors
| Status | code | Meaning |
|---|---|---|
400 | INVALID_IDEMPOTENCY_KEY | The key is not a UUID or 8–64 allowed characters. |
400 | IDEMPOTENCY_KEY_REQUIRED | This endpoint requires a key and none was sent. |
409 | IDEMPOTENCY_KEY_IN_PROGRESS | The first request with this key is still running. |
409 | IDEMPOTENCY_KEY_NOT_REPLAYABLE | The 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. |
422 | IDEMPOTENCY_KEY_MISMATCH | The key was used with a different method, path or body. |
503 | IDEMPOTENCY_UNAVAILABLE | The 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-Keyheader on the request. - TypeScript -- pass the header through the client's
headersoption. 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
5xxon 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
Errors
Error envelope, codes and retry guidance.
Versioning
Pin the API version your integration is built against.
API Reference
See which endpoints accept Idempotency-Key.
Owner: david · Reviewed 12 Oct 2026 · Next review 6 Nov 2026