> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tabsglobal.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency & Concurrency

> Prevent duplicate charges and payouts using bank-grade idempotency keys.

Network disruptions, timeout errors, or client-side retries can lead to duplicate requests. In financial integrations, retrying a payment without proper safeguards could result in double-charging a wallet or initiating duplicate bank transfers.

Tabs provides bank-grade idempotency protection on all mutating financial endpoints, including:

* `POST /v1/payouts` (Initiate bank payouts)
* `POST /v1/fx/swap` (Execute currency conversions)

***

## How idempotency works

To make a request idempotent, include a unique token in the `Idempotency-Key` HTTP header:

```http theme={null}
Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
```

Tabs also recognizes the alternative header `X-Idempotency-Key`.

### The lifecycle of an idempotent request

1. **First request**: Tabs locks the key and executes the transfer. The response is cached in our persistence layer for **24 hours**.
2. **Replayed request**: If you send an identical request with the same `Idempotency-Key` within 24 hours, Tabs bypasses processing and returns the cached response immediately.
3. **Replay indicator**: Replayed responses include the header:
   ```http theme={null}
   Idempotent-Replayed: true
   ```

***

## Code example

Always generate a cryptographically random UUID v4 for each distinct transaction attempt:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.tabsglobal.co/v1/payouts" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Idempotency-Key: e1a35bf9-2169-42b7-84e1-2c97693d25d0" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 50000,
      "sourceCurrency": "NGN",
      "payoutCurrency": "NGN",
      "bankCode": "058",
      "accountNumber": "0123456789",
      "recipientName": "Adewale Adeleke",
      "narration": "Vendor invoice #901"
    }'
  ```

  ```javascript Node.js theme={null}
  import { randomUUID } from "node:crypto";

  const idempotencyKey = randomUUID();

  async function sendPayout(payoutPayload) {
    const response = await fetch("https://api.tabsglobal.co/v1/payouts", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.TABS_SECRET_KEY}`,
        "Idempotency-Key": idempotencyKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payoutPayload),
    });

    const isReplayed = response.headers.get("Idempotent-Replayed") === "true";
    const data = await response.json();

    return { data, isReplayed };
  }
  ```

  ```python Python theme={null}
  import uuid
  import requests

  idempotency_key = str(uuid.uuid4())

  def send_payout(payload):
      headers = {
          "Authorization": f"Bearer {API_KEY}",
          "Idempotency-Key": idempotency_key,
          "Content-Type": "application/json",
      }
      response = requests.post(
          "https://api.tabsglobal.co/v1/payouts",
          headers=headers,
          json=payload,
      )
      is_replayed = response.headers.get("Idempotent-Replayed") == "true"
      return response.json(), is_replayed
  ```
</CodeGroup>

***

## Error handling

### 1. In-flight collision (`409 Conflict`)

If two requests arrive simultaneously with the exact same `Idempotency-Key` before the first finishes processing, Tabs rejects the second request:

```json theme={null}
{
  "statusCode": 409,
  "message": "A request with this idempotency key is currently processing. Please retry shortly.",
  "error": "Conflict"
}
```

### 2. Payload mismatch (`422 Unprocessable Entity`)

If you send a request with an existing `Idempotency-Key` but modify the request body (e.g. altering the amount or beneficiary), Tabs prevents accidental confusion and rejects the call:

```json theme={null}
{
  "statusCode": 422,
  "message": "Idempotency key was previously used with a different request payload.",
  "error": "Unprocessable Entity"
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.