Skip to main content
When building financial applications, preventing duplicate transactions is critical. The Formance Ledger provides two mechanisms to ensure transaction uniqueness: idempotency keys and references.

Idempotency Keys

Add an Idempotency-Key header to your request. If you execute the same request twice with the same key, the system will skip processing and return the original successful response.

How it works

  1. When you send a request with an idempotency key, the ledger stores a hash of the request along with the response
  2. If you retry with the same key and identical request, the ledger returns the cached response with an Idempotency-Hit: true header
  3. If you retry with the same key but different request parameters, the ledger returns a validation error

Supported endpoints

Idempotency keys work on all write endpoints:
  • Create transaction (including batch and Numscript)
  • Update metadata (account or transaction)
  • Revert transaction

Scope

Idempotency keys are scoped to a specific ledger. The same key can be used on different ledgers without conflict.

Example

curl -X POST https://api.formance.cloud/api/ledger/v2/{ledger}/transactions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: unique-key-123" \
  -H "Content-Type: application/json" \
  -d '{
    "postings": [{
      "source": "world",
      "destination": "users:alice",
      "amount": 100,
      "asset": "USD/2"
    }]
  }'

References

A reference is a field in the transaction body that acts as a unique identifier. If a transaction with the same reference already exists, the ledger returns an error.

Example

curl -X POST https://api.formance.cloud/api/ledger/v2/{ledger}/transactions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "order-12345-payment",
    "postings": [{
      "source": "world",
      "destination": "users:alice",
      "amount": 100,
      "asset": "USD/2"
    }]
  }'

Choosing Between Idempotency Keys and References

Use CaseRecommendedReason
Retry logic for network failuresIdempotency KeySystem returns success on retry
Mapping to external entities (e.g., orders, refunds)ReferenceGet an error if duplicate, useful for debugging
Unknown or dynamic transaction identityIdempotency KeyWorks with any unique value
Use a reference when you have a unique entity in your system that the transaction should match (e.g., a refund object creating a unique ledger transaction). You’ll get an error if you try to re-submit with the same reference.Use an idempotency key when the identity is less obvious or you want silent duplicate handling. The system will skip processing and return success on retry.

Validation Errors

Starting with Ledger v2.2, you will receive a VALIDATION error when reusing an idempotency key with different transaction parameters than the original request.

Error format

{
  "errorCode": "VALIDATION",
  "errorMessage": "invalid idempotency hash when using idempotency key 'unique-key-123', has computed 'abc123...' but 'xyz789...' is stored"
}

Common causes

  • Changing any field in the request body (including metadata)
  • Using a different endpoint path
  • Modifying the request after a previous successful call

Best practices

  1. Use unique keys for each distinct transaction - Generate a new key for each new transaction
  2. Keep request parameters consistent - If retrying, ensure all parameters match the original request exactly
  3. Use a new key if parameters change - If you need to modify the transaction, generate a new idempotency key
Changing any part of the transaction, including metadata, will trigger a validation error when reusing an idempotency key.
If a transaction was created with Ledger v2.1 or earlier, the idempotency hash stored in the database is empty. In this case, the hash validation check is skipped for backward compatibility.

Using Idempotency in Bulk Operations

When processing bulk transactions, you can specify idempotency keys for individual elements to enable safe retries after partial failures.

Script stream format

//script ik=transaction-001
send [USD 100] (
  source = @world
  destination = @alice
)
//end

JSON stream format

{
  "action": "CREATE_TRANSACTION",
  "ik": "transaction-001",
  "data": {
    "postings": [{
      "source": "world",
      "amount": 100,
      "asset": "USD",
      "destination": "alice"
    }]
  }
}
See Bulk Processing for more details on bulk operations.