Skip to main content

Idempotency

Definition

Idempotency ensures that repeated API requests do not cause unintended duplicate transactions.
It is a critical mechanism in payment processing to handle retries safely.

Why is Idempotency Important?

Requests to the Payment API may be accidentally duplicated due to:

  • Network failures, causing a retry.
  • Timeouts, leading the merchant system to resend the same request.
  • User actions, such as clicking multiple times on a "Pay" button.

Without idempotency, these scenarios could result in duplicate payments or refunds.

Send no key at all, and a retry issued after a lost response is indistinguishable from a new operation: it either executes a second time, or is rejected with a 400 once the requested amount is no longer available on the payment. Idempotency keys are strongly recommended for batch and retry-driven integrations.


Best Practices

  • Send x-idempotency-key on every state-changing call: payment creation (POST /v2/payments), capture, refund, and void.
  • One key per operation, never one key per payment. Keys are scoped to the payment, and a payment can legitimately be captured, voided, or refunded several times — two partial captures on the same authorization, for example. If two different operations on the same payment reuse the same key, the second is treated as a replay and refused with a 409 instead of being executed.
  • Derive the key from the operation itself — your own capture or refund identifier, or the payment ID plus a sequence number. Never derive it from the payment or the order alone.
  • Keep the key stable across every retry, including a retry sent hours or days later. A key regenerated per HTTP attempt provides no protection at all.
  • Retry with the same key when a response is lost (client timeout, network error, 5xx). The operation is then executed at most once.
  • Store keys for at least as long as your retry window, so a late retry can reuse the original key.

Example

Using Idempotency in a Capture Request

curl -X POST "https://api.purse-sandbox.com/payment/v2/payments/${PAYMENT_ID}/captures" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-idempotency-key: CAP#1223445" \
--data '{
"amount": 5000,
"merchant_reference": "CAP#1223445"
}'

The key is derived from the merchant's own capture reference, so every retry of this capture carries the same value, while a second capture on the same payment gets a key of its own.

What happens if the same idempotency key is reused?

A request carrying a key already processed on the same payment is answered with a 409 Conflict — whether it is a genuine retry or a different operation that reused the key by mistake:

{
"type": "urn:eu.purse:write-conflict",
"title": "Idempotency violation",
"status": 409,
"instance": "/v2/payments/558f47c6-fd88-4c28-aa2f-5d3d7a44709f/captures",
"resource_id": "432ccde8-13e8-4c31-a900-a9c40546c12a"
}

resource_id points at the resource the first call created: the payment ID for POST /v2/payments, or the capture, void, or refund operation ID for the operation endpoints. Read it back to recover the outcome of that first call.

Replays while the first call is still running

A replay arriving while the first call is still in flight is held for a bounded number of attempts, then answered like any other replay. If the first call never completes at all, the replay errors instead of resolving — so a retry that keeps failing with a 5xx should be settled by reading the payment back with GET /v2/payments/{id}, not by replaying the key again.