Core Concepts

Idempotency

How to retry a request without double-charging, using each integration's idempotency key.

Idempotency ensures that a payment request is processed only once, even if the same request is sent multiple times. When a network timeout or server error occurs, you can safely retry the request without risking a duplicate charge.

How it works

When Tonder receives a request with an idempotency key, it checks whether it has already processed a request with that key and an identical body:

  • First request — Tonder processes the payment and stores the response against the key.
  • Subsequent request with the same key and body — Tonder returns the stored response without creating a new transaction.
  • Request with the same key but a different body — Tonder rejects the request. Generate a new key for any modified payload.

By integration mode

The header differs by integration type:

IntegrationIdempotency headerFormatWindow
Hosted Checkoutx-idempotency-keyfree string (e.g. test-001)5 seconds
API DirectX-Request-IdUUID v4 (e.g. 550e8400-e29b-41d4-a716-446655440000)per request
SDKHandled by the SDKContact support for details

API Direct

Include the X-Request-Id header on every POST request to /process/. Use a UUID v4 for each distinct payment operation.

POST /api/v1/process/
Authorization: Token <YOUR_API_KEY>
X-Request-Id: <UNIQUE_IDEMPOTENCY_KEY>
Content-Type: application/json

See Authentication for full details on the Authorization header.

Generating and sending the key

Use a UUID v4 for every distinct payment operation. UUIDs are globally unique, easy to generate in any language, and safe to store for debugging.

import { v4 as uuidv4 } from 'uuid';

const idempotencyKey = uuidv4();
// Example: "550e8400-e29b-41d4-a716-446655440000"

const response = await fetch('https://stage.tonder.io/api/v1/process/', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_API_KEY',
    'X-Request-Id': idempotencyKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(paymentData)
});
import uuid
import requests

idempotency_key = str(uuid.uuid4())
# Example: "550e8400-e29b-41d4-a716-446655440000"

response = requests.post(
    'https://stage.tonder.io/api/v1/process/',
    headers={
        'Authorization': 'Token YOUR_API_KEY',
        'X-Request-Id': idempotency_key,
        'Content-Type': 'application/json'
    },
    json=payment_data
)
curl -X POST https://stage.tonder.io/api/v1/process/ \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "X-Request-Id: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "operation_type": "payment",
    "amount": 100.00,
    "currency": "MXN"
  }'

Hosted Checkout

When creating a session with POST /checkout/v1/sessions you can include the x-idempotency-key header (optional but recommended) to prevent duplicate sessions on network errors or retries. The default protection window is 5 seconds:

ScenarioResult
Same key, within 5 secondsReturns the same session — no duplicate created
Same key, after 5 secondsCreates a new session
Different keyAlways creates a new session

Reuse vs. regenerate the key

The rule is simple: the key must match the intent. Keep the same key when retrying the exact same payment after a failure. Generate a new key whenever any field in the body changes — amount, currency, payment method, or customer data.

const idempotencyKey = uuidv4();

async function processWithRetry(paymentData, key, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await processPayment(paymentData, key); // same key every attempt
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      // Exponential backoff before next retry
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
    }
  }
}

Generating a new key on each retry defeats idempotency protection and can result in duplicate charges. Store the key alongside your order record before sending the first request so you can retrieve it on retry.

Error handling

Best practices

  • Include X-Request-Id on every POST request to /process/, not just in retry logic — it protects against silent network failures.
  • Store idempotency keys in your database alongside the order before sending the request.
  • Use exponential backoff between retries (start at 1 second and double it).
  • Don't use predictable values (sequential integers, order ID alone, or timestamps) as keys — they increase the risk of collisions.
  • On a retry, the stored response may show Pending or Failed: verify the current status with Get Transaction Status rather than assuming the retry succeeded.

Next steps

Was this page helpful?

On this page