Skip to main content

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.

The Idempotency-Key Header

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"
  }'
EndpointIdempotency Key
POST /v2/transactionsRequired — request will be rejected without it
POST /v2/transactions/:id/payoutRecommended
Other POST endpointsRecommended

Key Format

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

ScenarioHTTP StatusResponse
New unique key201Transaction created
Same key, same body (retry)200Cached response from original request, with Idempotent-Replayed: true header
Same key, different body409Conflict — 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.