Idempotency
Send Idempotency-Key: <uuid> on any POST and the gateway will
replay the cached response when the same request arrives again
within 24 hours. This makes network-blip retries safe and prevents
double charges.
How it works
Section titled “How it works”- The gateway hashes the request body and stores
(idempotency_key, body_hash) → cached_responsein Redis with a 24-hour TTL. - A second request with the same key and same body returns the
cached response, with the same HTTP status, same
transaction_id, same everything. No new transaction is created. - A second request with the same key and a different body returns
409 Conflict. - A second request with a different key is treated as new and may create a new transaction.
1st POST key=A body={amount:12.50} → 200, txn_xyz, charges card2nd POST key=A body={amount:12.50} → 200, txn_xyz, idempotent replay3rd POST key=A body={amount:13.00} → 409, idempotency key reused with different body4th POST key=B body={amount:12.50} → 200, txn_NEW, second chargePicking a key
Section titled “Picking a key”Use a UUID v4 per logical request, not per HTTP attempt. The same key must be re-sent if you retry. Examples of good keys:
crypto.randomUUID()saved alongside your order in your database before you make the first call. Re-sent on every retry of that order’s checkout.- A deterministic hash of
(order_id, attempt_number)— but only ifattempt_numberdoesn’t change between retries.
Never use:
Date.now()— it changes per request, which defeats the purpose.- The order ID alone — if a customer retries an abandoned checkout an hour later, you don’t want the cached failure response.
Where it applies
Section titled “Where it applies”| Endpoint | Idempotency-Key honoured? |
|---|---|
POST /internal/sessions/create | Yes |
POST /payments/collect | Yes |
POST /payments/collect-redirect | Yes |
POST /payments/send | Yes |
POST /payments/submit | Yes |
POST /payments/verify-otp | Honoured but rarely needed (OTP codes are single-use) |
GET ... | N/A — GET is naturally idempotent |
The Flutter SDK auto-attaches Idempotency-Key (UUID v4) to every
POST it issues. You can pre-set one via Options.headers if you
need to control it externally.
The Angular SDK does not auto-attach Idempotency-Key — set it
manually via PdirectHttpClientService if you need it:
// Pseudocode — until Angular SDK v2 wires this automaticallythis.http.configure({ customHeaders: { "Idempotency-Key": crypto.randomUUID() },});What’s NOT consumed
Section titled “What’s NOT consumed”A cache hit doesn’t count against your rate-limit budget. The order is:
Request → RateLimiter → IdempotencyMiddleware → Handler │ ├─ cache hit? → return cached response └─ cache miss? → invoke handler, cache resultSo replaying through a flaky network does not eat into your
30/min budget.
Failure modes worth knowing
Section titled “Failure modes worth knowing”- Different body, same key → 409. The error envelope’s
error_codeis"1110"(INVALID_FIELD_FORMAT) and themessagewill mention idempotency reuse. Pick a new UUID. - Cached error responses. If your first attempt failed with a
4xx, the cache stores the failure. Retrying with the same key replays the failure. To genuinely retry the operation, use a new key. - TTL is 24 h. After that the key is forgotten. Long-tail duplicate prevention requires your own database deduplication.
See also
Section titled “See also”- Errors —
1110/1301/ retry semantics - Rate limits — why idempotent replays don’t cost