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

# Idempotency

> How to make every retryable request safe to retry — the idempotencyKey pattern.

Network failures are not "rare edge cases" — at sufficient request volume they happen daily. The single most important pattern to keep your integration correct under failure is **idempotency**: making it safe to retry the same operation any number of times without producing duplicate side effects.

## The problem

Consider a "create payment order" request that returns a network error:

```
POST /wallets/production-main/paymentOrders   →   network timeout
```

You face an ambiguity:

* Did the server receive the request and process it? → Retry creates a **duplicate payment**.
* Did the request fail before the server saw it? → Not retrying leaves you with a **missing payment**.

Without an idempotency key, you cannot tell. Both choices are wrong sometimes.

## The solution: `idempotencyKey`

When you create a payment order, supply an `idempotencyKey` — a client-generated identifier unique to the *intent* of the operation:

```json theme={"dark"}
POST /wallets/production-main/paymentOrders
{
  "idempotencyKey": "invoice-2026-0184",
  "direction": "IN",
  "amount": 25000,
  "currency": "BRL",
  "network": "br.gov.bcb.pix",
  "instrument": { "type": "PIX_CASH_IN_EMV_DYNAMIC", "expiresIn": 86400 }
}
```

The server tracks `(wallet, idempotencyKey)` tuples:

| Scenario                                                          | Result                                                                 |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
| First request with this `(wallet, idempotencyKey)`                | Order is created. `201 Created`.                                       |
| Retry with **identical body**                                     | The original order is returned (same `id`, same state). `201 Created`. |
| Retry with **divergent body** (different amount, recipient, etc.) | `IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS` (HTTP 422).             |

Retries become **safe**. You can retry as many times as needed without creating duplicates.

## Choosing an `idempotencyKey`

Tie the key to the **business intent**, not the network attempt.

| Good keys                                                    | Why                                             |
| ------------------------------------------------------------ | ----------------------------------------------- |
| `invoice-2026-0184`                                          | One per invoice — each invoice should pay once. |
| `payroll-2026-01-emp-1729`                                   | One per employee per pay period.                |
| `refund-${orderId}`                                          | Refunding the same order is idempotent.         |
| UUID v4 generated when persisting the payment intent locally | Stable across retries because it is persisted.  |

| Bad keys                   | Why                                          |
| -------------------------- | -------------------------------------------- |
| `new Date().toISOString()` | Different on every retry → no deduplication. |
| `Math.random()`            | Different on every retry.                    |
| `request-${attempt}`       | Encodes the retry number → no deduplication. |

### The persist-then-call pattern

The safest implementation:

<Steps>
  <Step title="Persist the intent locally">
    Before you call the API, write a row in your database describing the operation, with a stable `idempotencyKey` (UUID v4 is fine).
  </Step>

  <Step title="Call the API with that key">
    Include the persisted key in the request body.
  </Step>

  <Step title="On any error">
    Retry — read your persisted key from the local row, do not regenerate.
  </Step>

  <Step title="On success">
    Update your local row with the returned `id` and current `status`.
  </Step>
</Steps>

This pattern survives application crashes between any of the steps.

## Scope and lifetime

| Property      | Detail                                                                                          |
| ------------- | ----------------------------------------------------------------------------------------------- |
| Scope         | Unique within a wallet. `(wallet, idempotencyKey)` is the tuple.                                |
| Length        | 1 to 64 characters.                                                                             |
| Character set | Recommend `[a-zA-Z0-9_-]`. Whitespace and special characters work but make logs harder to grep. |
| Lifetime      | Retained indefinitely. Reusing a key from months ago will still match the original order.       |

<Note>
  Because keys are retained indefinitely, scope them carefully. `payment-1` is a poor key — it will collide eventually. Prefix with a meaningful namespace: `invoice-2026-0184`.
</Note>

## When you supply different params on retry

The most common cause of `IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS` is **not** a bug in your retry logic — it is reusing a key across different invoices.

If you genuinely need to retry with corrected parameters (e.g., the original amount was wrong):

1. Treat the original key as **burned**.
2. Mint a **new** `idempotencyKey` for the corrected attempt.
3. Reconcile against the original key via your local persistence.

There is no "force overwrite" operation. The first set of params for a key is final.

## What if I do not supply an `idempotencyKey`?

The OpenAPI spec marks `idempotencyKey` as optional. Omitting it means **every retry creates a new order**. For PIX in particular this means **every retry potentially creates a duplicate payment**.

<Warning>
  **For any operation with financial side effects, always supply an `idempotencyKey`.** The cost of generating one is trivial; the cost of a duplicate transfer to a customer can be material.
</Warning>

## What is NOT idempotent

| Operation                                           | Why not                                                                                            |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `POST .../paymentOrders` (without `idempotencyKey`) | Creates a new order on every call.                                                                 |
| `PUT .../approve`                                   | Once approved, the order is `PENDING`. Re-approving returns `PAYMENT_ORDER_NOT_AWAITING_APPROVAL`. |
| `PUT .../cancel`                                    | Once canceled, re-canceling returns `PAYMENT_ORDER_INVALID_STATE`.                                 |

For `approve` and `cancel`, the *operation* is idempotent in effect — calling twice does not duplicate the state transition; the second call merely returns an error indicating the resource is no longer in the right state. Treat the error as success when you know you intended the transition.

## Idempotency vs request retry signature

Each retry attempt still needs a **new** `X-Access-Request-Id` — that is the signing protocol's anti-replay nonce, separate from `idempotencyKey`:

| Field                 | Granularity         | Reused on retry?                                    |
| --------------------- | ------------------- | --------------------------------------------------- |
| `idempotencyKey`      | Per business intent | ✅ **Yes** — reuse to deduplicate                    |
| `X-Access-Request-Id` | Per network attempt | ❌ **No** — mint fresh, or you get `REPLAY_DETECTED` |

See [Sign a request](/get-started/authentication/sign-request) for the request-id rules.

## Next

<CardGroup cols={2}>
  <Card title="Retry strategy" icon="rotate-right" href="/get-started/errors/retry-strategy">
    When to retry and how to back off.
  </Card>

  <Card title="Payments overview" icon="money-bill-transfer" href="/get-started/payments/overview">
    How idempotency interacts with the payment-order lifecycle.
  </Card>
</CardGroup>
