Skip to main content
This page explains how Midaz protects against duplication with idempotency keys, and how to use them when you retry a request.

Making retries safe

Failed API requests are common. A network timeout occurs, or your service goes down right after it sends a request. Retrying is natural, and you need to be sure Midaz will not process the same operation twice. By attaching a unique key to each request, you’re telling Midaz: “This is the same operation. If you’ve already processed it, don’t do it again.” Midaz stores this key temporarily and uses it to determine whether the request is new, complete, or still in progress. This protects your system from duplicates.

Idempotency in Midaz


The X-Idempotency header is available on every transaction create endpoint: in /v1, json, inflow, outflow, annotation, block, and unblock; in /v2, direct, hold, block, unblock, and batch. Revert takes no key of its own: Midaz derives one.
Other Lerian APIs use their own idempotency headers, or none. This page focuses on idempotency for the Midaz Ledger API.
The commit and cancel transaction endpoints use a Redis-based lock to prevent concurrent processing of the same transaction, but they do not support full idempotency (no cached responses or X-Idempotency-Replayed header).
To use it, your request can include two headers:
  • X-Idempotency: the unique key that identifies the request.
  • X-TTL: the time-to-live (in seconds) that Midaz should store this key in cache.
Here’s what happens:
  1. When a new key arrives, Midaz marks it as pending, processes the request, and stores the full response in cache.
  2. If you send the same key again within the TTL window:
    1. While the operation runs, Midaz returns a 409 Conflict (error code 0084).
    2. If it’s done and the request matches, Midaz returns the exact same response with a 201 Created status code and X-Idempotency-Replayed: true.
  3. If the transaction fails due to validation or insufficient balance errors, Midaz deletes the idempotency key. You can then fix the issue and retry with the same key.
Midaz scopes idempotency keys by organization and ledger. You can therefore use the same key value independently across different organizations or ledgers without conflict.

Workflow summary

Figure 1 shows the full lifecycle of an idempotent request:
Full lifecycle of an idempotent request in Midaz, from receiving the idempotency key to returning the cached or newly processed response

Figure 1. Idempotent request lifecycle in Midaz.

How it works:
  • If the request doesn’t include an existing idempotency key, Midaz creates a new one. Midaz then processes the request, stores the response, and returns it with X-Idempotency-Replayed: false.
  • If the key already exists:
    • While the operation runs, Midaz returns a 409 Conflict.
    • If the operation is complete and the request matches, Midaz skips execution and returns the cached response with a 201 Created status code and X-Idempotency-Replayed: true.
  • If the original request failed due to validation or balance errors, Midaz deletes the key automatically. You can safely retry with the same key.

Example request

Here’s how to send an idempotent request to create a transaction:
If the request succeeds and you send it again within 60 seconds, Midaz will return the cached result with:
If you send it again while Midaz still processes the original, you’ll receive:

Key generation


You can provide your own X-Idempotency key or let Midaz generate one automatically.

Automatic key generation

If you omit the X-Idempotency header, Midaz treats the same body sent to the same endpoint within the TTL window as a repeat. This is sufficient for most retry scenarios where the request body doesn’t change between attempts.

Custom key generation

Use a custom key when you need to:
  • Correlate the idempotency key with an ID in your own system (e.g., an order ID).
  • Retry with a modified request body while still deduplicating (e.g., after correcting a field).
  • Control the key format for logging or auditing purposes.
The key should be deterministic: if you retry the same logical operation, the key remains the same. A common approach is to use a UUID or a hash based on your internal reference:

Best practices


Always validate the X-Idempotency-Replayed header

When your system receives a response from a transaction endpoint, always check the X-Idempotency-Replayed response header before processing the result. This header tells you whether the response is from a new operation or a cached replay:
  • X-Idempotency-Replayed: false: This is a fresh response. Midaz just processed the transaction.
  • X-Idempotency-Replayed: true: This response comes from cache. Midaz already processed the transaction.
Failing to check this header is a common integration mistake. Without it, your system may interpret a replayed response as a new transaction. That creates duplicate processing on your side, even though Midaz only executed it once.
For example, if your system settles boletos (bank slips) based on transaction responses, you must verify X-Idempotency-Replayed to avoid settling the same boleto twice.

Use explicit idempotency keys for critical flows

For critical financial flows (settlements, payouts, transfers), always provide an explicit X-Idempotency key tied to your business process ID. This gives you:
  • After the first request succeeds, a retry with a changed body within the TTL window gets 409 (0084) instead of a second transaction.
  • A clear audit trail linking Midaz transactions to your internal operations.
  • Protection against edge cases where request serialization might differ.

Set appropriate TTL values

Choose TTL values that match your retry window:
  • For synchronous operations with fast retries: 60–120 seconds.
  • For asynchronous workflows with potential delays: 300–600 seconds.
  • For batch processing with long retry windows: consider longer TTLs and explicit keys.

Retry strategy


Use these patterns for different failure scenarios:

Retryable failures

These failures are safe to retry with the same idempotency key:

Non-retryable failures

These failures require a different approach:

Exponential backoff

For transient errors, use exponential backoff with jitter to avoid overwhelming the server.

Preventing entity duplication


For some endpoints, you don’t need idempotency keys to avoid duplication. Midaz enforces uniqueness constraints on critical resources. If you attempt to create an entity that conflicts with an existing one, the system blocks the request and returns a 409 Conflict with a descriptive error: Unlike idempotency keys, these constraints are permanent and don’t expire.

Idempotency across Lerian products


Other Lerian APIs set their own idempotency headers, scopes, and replay signals, and some accept no idempotency header. Check the API reference of the product you call before you retry a command.

FAQ


Midaz treats the same body sent to the same endpoint within the TTL window as a repeat. You only need to provide a custom key if you want to control deduplication independently from the request body.
Yes. Midaz scopes idempotency keys per organization and ledger. The same key value used in different organizations or ledgers won’t conflict. However, within the same organization and ledger, each key must be unique per operation.
No. Use a unique key for each distinct operation.
Midaz uses only the TTL from the first request. Changing it later has no effect.
Yes. Midaz replays the full response, including status code (201 Created) and body, for completed requests.
The default window is 300 seconds (5 minutes).
If the transaction fails due to validation errors or insufficient balance, Midaz deletes the idempotency key from cache. You can then fix the issue and retry with the same key.