Idempotency
Every Soxara endpoint that creates or moves money supports — and requires — an idempotency key. Send the same key twice, get the same result back. The operation runs exactly once.
Why it matters
The internet is unreliable. Your server posts a payment to Soxara. Soxara processes it. Then the response gets dropped on the way back to your server. Your server doesn’t know if the payment went through, so it retries. Without idempotency, you’d charge the customer twice.
With idempotency, Soxara recognizes the second request as a retry, returns the original response (success or failure), and the customer is only charged once.
How to use it
Pass an X-Idempotency-Key header on every request that creates a resource:
curl -X POST "$SOXARA_BASE/v1/payments" \
-H "Authorization: Bearer $SOXARA_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 1250, "currency": "USD"}'Rules:
- Format: any UUID (v4 recommended), or any string up to 255 chars that’s unique-per-operation in your system.
- Lifetime: idempotency keys are valid for 24 hours after first use. After 24h the same key can be reused without colliding with the original.
- Scope: keys are scoped per-merchant. Two different merchants can use the same key without conflict.
Which endpoints require it
Any endpoint that creates a financial side-effect:
POST /v1/payments— create paymentPOST /v1/refunds— refund a paymentPOST /v1/transfers— wallet-to-wallet sendPOST /v1/gift-cards— issue a gift cardPOST /v1/bill-payments— pay LEC/LWSC/etc.
GET requests are naturally idempotent; you don’t pass the header.
What happens on collision
If you POST with X-Idempotency-Key: abc-123 and then POST the same key with a different body, Soxara rejects the second request with:
{
"success": false,
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "Idempotency key already used with a different request body."
}
}This protects you from accidentally reusing a key while your code mutates the payload. Use a fresh UUID per logical operation; don’t reuse keys across operations.
What happens on replay
Same key + same body within 24h returns the original response, byte-for-byte (including the original status code). This is true even if the original request is still in flight — the replay blocks until the original resolves, then returns its result.
If the original returned 409 IDEMPOTENCY_CONFLICT, the replay returns the same 409. If the original succeeded with 200, the replay returns the same 200 and the same payload (same payment ID, same timestamps).
Recommended pattern
Generate the key once per logical operation in your code, save it before making the API call, and reuse it on every retry:
async function chargeCustomer(orderId: string, amount: number) {
// Idempotency key tied to the order, not the request. If our process
// crashes between generating the key and calling Soxara, we'll
// recover the same key on retry and Soxara won't double-charge.
const idempotencyKey = `order-${orderId}-charge-v1`;
return fetchWithRetry(
`${SOXARA_BASE}/v1/payments`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${SOXARA_KEY}`,
'X-Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount, currency: 'USD' }),
},
);
}If you change the amount or any other field of the request, change the key suffix (-charge-v2). Otherwise you’ll get an IDEMPOTENCY_CONFLICT.