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.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.
The problem
Consider a “create payment order” request that returns a network error:- 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.
The solution: idempotencyKey
When you create a payment order, supply an idempotencyKey — a client-generated identifier unique to the intent of the operation:
(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). |
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: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).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. |
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 ofIDEMPOTENCY_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):
- Treat the original key as burned.
- Mint a new
idempotencyKeyfor the corrected attempt. - Reconcile against the original key via your local persistence.
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.
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. |
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 newX-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 |
Next
Retry strategy
When to retry and how to back off.
Payments overview
How idempotency interacts with the payment-order lifecycle.