Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Idempotency Guidelines

Idempotency ensures that repeating the same API request produces the same result without duplicating operations. This is critical for payment operations where network failures, timeouts, or retries could otherwise create duplicate transactions.


Why Idempotency Matters

In distributed systems, requests can fail or timeout without you knowing whether they succeeded:

  • Network timeout: Request succeeded but response was lost
  • Server error: Request partially processed before failure
  • Client retry: User clicks submit button multiple times

Without idempotency, retrying these requests could create duplicate payments, causing financial discrepancies and poor user experience.


How It Works

  1. Generate a unique key for each intended operation (UUID v4 recommended)
  2. Include the key in the Idempotency-Key header of your POST request
  3. Server checks if it has seen this key before:
    • If new: processes the request and stores the result with the key
    • If seen with same body: returns the stored result
    • If seen with different body: returns 409 IDEMPOTENCY_CONFLICT
  4. Safe to retry the same request if you don’t receive a response

Important: Keys are valid for 24 hours. After this period, the same key can be used for a new request.


Required Headers

POST /api/v1/pay-in/deposit-creation
Content-Type: application/json
Authorization: Basic {base64(businessKey:businessSecret)}
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
HeaderRequiredDescription
Idempotency-KeyYes (POST)Unique identifier for this operation. UUID v4 format recommended

Generating Idempotency Keys

Use a UUID v4 generator built into your programming language:

Node.js
const { randomUUID } = require('crypto');
const idempotencyKey = randomUUID();
// Output: "550e8400-e29b-41d4-a716-446655440000"
# Python
import uuid
idempotency_key = str(uuid.uuid4())
# Output: "550e8400-e29b-41d4-a716-446655440000"
// PHP
$idempotencyKey = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
// Or use: Ramsey\Uuid\Uuid::uuid4()->toString();

Alternative: Deterministic Keys

For certain use cases, you may want keys that are reproducible based on your business logic:

// Deterministic key based on operation context
const crypto = require('crypto');
function generateDeterministicKey(userId, action, timestamp) {
const data = `${userId}:${action}:${timestamp}`;
return crypto.createHash('sha256').update(data).digest('hex');
}
// Example: same inputs always produce same key
const key = generateDeterministicKey('user_123', 'deposit', '2024-01-15T10:30:00Z');

Example: Safe Retry Pattern

async function createDepositWithRetry(depositData, maxRetries = 3) {
// Generate key ONCE for all retry attempts
const idempotencyKey = crypto.randomUUID();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.cashela.com/api/v1/pay-in/deposit-creation', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${Buffer.from(`${BUSINESS_KEY}:${BUSINESS_SECRET}`).toString('base64')}`,
'Idempotency-Key': idempotencyKey // Same key on every retry
},
body: JSON.stringify(depositData)
});
const result = await response.json();
if (response.ok && result.success) {
return result.data;
}
// Check if error is retryable
if (result.error?.code === 'RATE_LIMITED' || response.status >= 500) {
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await sleep(delay);
continue;
}
}
throw new Error(`API Error: ${result.error?.message || 'Unknown error'}`);
} catch (networkError) {
// Network error - safe to retry with same idempotency key
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await sleep(delay);
continue;
}
throw networkError;
}
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Error Responses

409 Conflict - Idempotency Key Reused with Different Body

{
"success": false,
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "Idempotency key has already been used with a different request body",
"details": {
"original_request_id": "req_01HJ3KBCD8E9F0G1H2I3J4K5L6",
"key": "550e8400-e29b-41d4-a716-446655440000"
}
},
"request_id": "req_01HJ3KBCD8E9F0G1H2I3J4K5L7"
}

Resolution: Generate a new idempotency key for the new request, or send the exact same request body.

400 Bad Request - Missing or Invalid Key

{
"success": false,
"error": {
"code": "INVALID_IDEMPOTENCY_KEY",
"message": "Idempotency-Key header is required and must be a valid UUID v4"
},
"request_id": "req_01HJ3KBCD8E9F0G1H2I3J4K5L8"
}

Best Practices

✅ Do

  1. Generate a new key for each unique operation

    // Good: New key for each deposit
    const depositKey = crypto.randomUUID();
  2. Store key-response mappings for reconciliation

    // Store in your database
    await db.idempotencyLogs.create({
    key: idempotencyKey,
    operation: 'deposit',
    requestBody: depositData,
    responseId: result.data.id,
    createdAt: new Date()
    });
  3. Reuse the same key when retrying after timeout/error

    // Key is generated once, outside the retry loop
    const idempotencyKey = crypto.randomUUID();
    for (let attempt = 0; attempt < maxRetries; attempt++) {
    // Use same idempotencyKey for all attempts
    }
  4. Include idempotency key in all POST requests

    • Deposit creation
    • Transaction creation
    • Payout initiation

❌ Don’t

  1. Don’t generate a new key on each retry

    // BAD: This defeats the purpose of idempotency
    for (let attempt = 0; attempt < maxRetries; attempt++) {
    const key = crypto.randomUUID(); // Wrong!
    await makeRequest(key);
    }
  2. Don’t use predictable or sequential keys

    // BAD: Easily guessable
    const key = `deposit-${userId}-${counter++}`;
  3. Don’t reuse keys for different operations

    // BAD: Same key for different deposits
    const globalKey = 'my-app-key';
    await createDeposit(data1, globalKey);
    await createDeposit(data2, globalKey); // Will fail!

Implementation Checklist

  • Generate UUID v4 keys for all POST requests
  • Store idempotency keys with request/response data
  • Implement retry logic that reuses the same key
  • Handle IDEMPOTENCY_CONFLICT errors appropriately
  • Log idempotency keys for debugging and support
  • Set up monitoring for idempotency-related errors