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, PATCH, PUT. GET and DELETE do not honour the header. |
| Recommended value | UUID v4 (36 chars). |
| Accepted value | Any string, 1–255 chars, scoped per API key. |
| Lifetime | 24 hours from first use. |
| Expiry behaviour | After expiry the key is forgotten; reusing it starts a new mutation. |
Replay semantics
When RevKeen receives a request with an Idempotency-Key it has seen before in the last 24 hours:
- Same key + same body + same path → return the stored response (status, body, headers). The underlying resource is not mutated again.
- Same key + different body →
409 Conflictwitherror.code = "idempotency_conflict". The original stored response is not returned. - Same key on a different endpoint → treated as a new mutation; keys are scoped per method + path.
The Idempotency-Replayed: true response header is set when you receive a stored response.
Which errors are stored
Only final responses are cached. In-flight state (for example a pending gateway call) is not cached, so an immediate retry during processing may return 409 idempotency_in_progress — back off briefly and retry.
| Scenario | Stored? | Retry behaviour |
|---|---|---|
2xx success | Yes | Retry returns the stored success response. |
4xx validation / permission error | Yes | Retry returns the same 4xx. |
5xx after the gateway call completed | Yes | Retry returns the stored 5xx. |
5xx before the gateway call completed | No | Retry may re-attempt the gateway call. |
| Network timeout (no response received) | No | Retry is safe — the key will either match or replay. |
Interaction with SDK auto-retries
All official SDKs automatically attach an Idempotency-Key to safe-to-retry requests and transparently retry 5xx, 429, and network errors up to 3 times.
If you set Idempotency-Key yourself, the SDK uses your value. You only need to override this for cross-process retry scenarios (queue worker replays, durable workflows).
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 '{ "charge": "ch_01HT...", "amount": 1500 }'import { RevKeen } from "@revkeen/sdk";
import { randomUUID } from "node:crypto";
const client = new RevKeen({ apiKey: process.env.REVKEEN_API_KEY! });
const refund = await client.refunds.create(
{ charge: "ch_01HT...", amount: 1500 },
{ idempotencyKey: randomUUID() },
);import (
"github.com/google/uuid"
revkeen "github.com/RevKeen/sdk-go"
)
refund, err := client.Refunds.Create(ctx, &revkeen.RefundCreateParams{
Charge: revkeen.String("ch_01HT..."),
Amount: revkeen.Int64(1500),
}, revkeen.WithIdempotencyKey(uuid.NewString()))use RevKeen\RevKeenClient;
use Ramsey\Uuid\Uuid;
$client = new RevKeenClient(['api_key' => getenv('REVKEEN_API_KEY')]);
$refund = $client->refunds->create(
['charge' => 'ch_01HT...', 'amount' => 1500],
['idempotency_key' => Uuid::uuid4()->toString()]
);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 — that produces a
409 idempotency_conflict. - Expect
5xxto be replayed on retry. A stored error is not a reason to abandon; surface it to the operator so they can decide whether to retry with a new key.