Documentation Index
Fetch the complete documentation index at: https://docs.stablepay.global/llms.txt
Use this file to discover all available pages before exploring further.
Why Idempotency Matters
In payment systems, network failures and timeouts are inevitable. Without idempotency, retrying a failed request could create a duplicate transaction — charging a user twice or triggering two payouts. Idempotency keys let you safely retry requests with the guarantee that the operation only executes once.
Include an Idempotency-Key header on mutating requests. The key uniquely identifies the intent of the request — same key, same result.
curl -X POST https://api.stablepay.global/v2/transactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"type": "sell",
"userId": "usr_abc123",
"amount": "100",
"asset": "USDT",
"network": "polygon"
}'
| Endpoint | Idempotency Key |
|---|
POST /v2/transactions | Required — request will be rejected without it |
POST /v2/transactions/:id/payout | Recommended |
| Other POST endpoints | Recommended |
Use a UUID v4 for each unique operation:
const idempotencyKey = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
import uuid
idempotency_key = str(uuid.uuid4())
Generate a new key for each distinct operation. Do not reuse a key from a previous transaction for a new one — this will return the cached response from the original transaction.
Key Expiry
Keys expire after 24 hours. After expiry, the same key can be used for a new request.
Behavior
| Scenario | HTTP Status | Response |
|---|
| New unique key | 201 | Transaction created |
| Same key, same body (retry) | 200 | Cached response from original request, with Idempotent-Replayed: true header |
| Same key, different body | 409 | Conflict — no action taken |
Successful Retry
When you retry with the same key and body, the response includes a header indicating it’s a replay:
HTTP/1.1 200 OK
Idempotent-Replayed: true
The response body is identical to the original 201 response.
Conflict (Different Body)
If you accidentally reuse a key with different parameters:
{
"error": "Idempotency Conflict",
"message": "This idempotency key was already used with a different request body",
"code": "IDEMPOTENCY_CONFLICT"
}
Generate a new key and retry.
Retry Example
async function createTransactionWithRetry(payload, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.stablepay.global/v2/transactions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify(payload)
});
if (response.status === 429) {
const retryAfter = (await response.json()).retryAfter ?? 60;
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return await response.json();
} catch (err) {
if (attempt === maxRetries) throw err;
await new Promise(r => setTimeout(r, 1000 * attempt));
}
}
}
The key is generated once outside the loop. Every retry sends the same key, so even if a previous attempt succeeded but the response was lost, the retry returns the original result without creating a duplicate.
Recovering a Lost Transaction ID
If your client crashes after sending a request but before saving the transactionId, you can recover it by replaying the same idempotency key and body:
# Replay the exact same request with the same key
curl -X POST https://api.stablepay.global/v2/transactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{ "type": "sell", "userId": "usr_abc123", "amount": "100", "asset": "USDT", "network": "polygon" }'
If the original request succeeded, you’ll get back the original response (including the transactionId) with the Idempotent-Replayed: true header. This works as long as the key hasn’t expired (24 hours).
Store your idempotency keys alongside the operation they represent. This makes recovery straightforward — if in doubt, just replay the request.