Idempotency
Use idempotency keys to safely retry mutating requests without creating duplicate payments or refunds.
Network calls fail in ambiguous ways — a request times out, but did the server process it? Idempotency keys let you retry safely: a repeated request with the same key returns the original result instead of creating a duplicate.
How it works
Send a unique X-Idempotency-Key header on mutating requests:
X-Idempotency-Key: order-7831-attempt-1ShadhinPay remembers the key and the request for 24 hours:
- Same key, same body → you get the original response back, verbatim. The
response carries
Idempotent-Replay: trueso you can tell it was a replay. No new payment is created. - Same key, different body →
409 ConflictwitherrorType: IDEMPOTENCY_CONFLICT. The key is already bound to a different request. - New key → processed normally.
After 24 hours a key is forgotten and may be reused.
Where it applies
Idempotency keys are honoured on the mutating, money-moving endpoints:
POST /paymentsPOST /payments/{paymentId}/refunds
Use a fresh key per logical operation
Generate one key per intent — e.g. per checkout attempt or per order. Reuse the same key when retrying that operation, but use a new key for a genuinely new operation. A UUID per attempt is a good default.
How matching works
ShadhinPay compares requests by hashing a canonical form of the body (keys sorted, whitespace normalised). This means a re-serialised but semantically identical body still matches — but any real change to the payload (a different amount, a different order ID) counts as a different request and triggers the conflict response.
A safe retry pattern
key = uuid() # once, per checkout attempt
loop:
response = POST /payments with X-Idempotency-Key: key
if response is a network error or 5xx:
wait (with backoff) and retry with the SAME key
else:
break # 2xx or a definitive 4xx — stop retryingBecause the key is stable across retries, you'll never double-charge a customer even if the first attempt actually succeeded but you never saw the response.