Idempotency
Network failures happen. If your request to create a payment times out you cannot know whether the payment was created. Retrying blindly could create two payments for one order. The Idempotency-Key header makes retries safe.
POST /api/v1/payments
Idempotency-Key: order-CLOUD-12345
Behaviour
| Situation | Result |
|---|---|
| First request with a key | Payment created, 201, Idempotency-Replayed: false |
| Same key, same body | Existing payment returned, 200, Idempotency-Replayed: true — same payment_id and payment_url |
| Same key, different body | 409 IDEMPOTENCY_CONFLICT with error.details.payment_id; nothing created |
| Same key, different application | Independent — keys are scoped per application |
| Concurrent duplicate requests | Exactly one payment is created; the other request returns it |
Creation failed at the gateway (GATEWAY_ERROR / GATEWAY_UNAVAILABLE) |
The key is released; a retry with the same key creates a fresh attempt |
"Same body" means the same amount, currency, order_id, callback_url, description and metadata (key order does not matter).
Choosing a key
- ≤ 191 characters,
A-Z a-z 0-9 _ - . :. - Derive it from your own attempt identity:
order-{orderId}, ororder-{orderId}-attempt-{n}if you allow the customer to retry after a failed/expired payment. - Keys never expire, so a permanently unique key per attempt is the safest choice.
Client pattern
$key = "order-{$order->id}-attempt-{$order->payment_attempts}";
try {
$payment = CartinoPay::createPayment($payload, idempotencyKey: $key);
} catch (ConnectionException) {
// Safe to retry with the same key.
$payment = CartinoPay::createPayment($payload, idempotencyKey: $key);
}
Verification is idempotent too
POST /payments/{id}/verify needs no key: repeated calls always converge on the same result and never double-process a paid payment.