Skip to main content

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.

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:
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:
ScenarioResult
First request with this (wallet, idempotencyKey)Order is created. 201 Created.
Retry with identical bodyThe 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 keysWhy
invoice-2026-0184One per invoice — each invoice should pay once.
payroll-2026-01-emp-1729One per employee per pay period.
refund-${orderId}Refunding the same order is idempotent.
UUID v4 generated when persisting the payment intent locallyStable across retries because it is persisted.
Bad keysWhy
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:
1

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).
2

Call the API with that key

Include the persisted key in the request body.
3

On any error

Retry — read your persisted key from the local row, do not regenerate.
4

On success

Update your local row with the returned id and current status.
This pattern survives application crashes between any of the steps.

Scope and lifetime

PropertyDetail
ScopeUnique within a wallet. (wallet, idempotencyKey) is the tuple.
Length1 to 64 characters.
Character setRecommend [a-zA-Z0-9_-]. Whitespace and special characters work but make logs harder to grep.
LifetimeRetained indefinitely. Reusing a key from months ago will still match the original order.
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.

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.
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.

What is NOT idempotent

OperationWhy not
POST .../paymentOrders (without idempotencyKey)Creates a new order on every call.
PUT .../approveOnce approved, the order is PENDING. Re-approving returns PAYMENT_ORDER_NOT_AWAITING_APPROVAL.
PUT .../cancelOnce 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:
FieldGranularityReused on retry?
idempotencyKeyPer business intentYes — reuse to deduplicate
X-Access-Request-IdPer network attemptNo — mint fresh, or you get REPLAY_DETECTED
See Sign a request for the request-id rules.

Next

Retry strategy

When to retry and how to back off.

Payments overview

How idempotency interacts with the payment-order lifecycle.