Idempotency

Use x-idempotency-key to make safe retries for create requests without causing duplicate operations.

Idempotency ensures that retrying a failed request does not result in duplicate operations — particularly critical for financial transactions where double-execution can cause real harm.


How idempotency works in NadaPay

When you send a POST request to create a resource, include a unique x-idempotency-key header. If the request fails or times out and you retry it with the same key, NadaPay will return the original response instead of creating a duplicate.

curl --request POST \
  --url https://core.nadapay.io/v1/transactions/execute \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'x-idempotency-key: a1b2c3d4-e5f6-7890-abcd-ef1234567890' \
  --data '{
    "quoteId": "qte_01HXXXXXXXXXXXXXXXXXX",
    "reason": "gift",
    "reasonDescription": "Invoice payout",
    "metadata": {
      "reference": "payout-invoice-0042"
    },
    "source": {
      "accountId": "acc-uuid-123",
      "nadapayCode": "WAL-123",
      "amount": 1000000
    },
    "destination": {
      "beneficiaryAccountId": "bac_01HXXXXXXXXXXXXXXXXXX",
      "accountId": "acc-destination-123",
      "nadapayCode": "WAL-456"
    }
  }'

If you send this exact request twice with the same x-idempotency-key, NadaPay returns the result of the first request on the second call — no duplicate transaction is created.


When to send an x-idempotency-key

MethodSend x-idempotency-key?
POST (create)✅ Always
PATCH (update)❌ No
GET❌ No
DELETE❌ No
⚠️

Sending an x-idempotency-key on a GET request will return a 400 Bad Request error.


How to generate an x-idempotency-key

Use a UUID (v4) generated fresh for each logical action:

// Node.js
const { randomUUID } = require('crypto');
const idempotencyKey = randomUUID();
// → 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
# Python
import uuid
idempotency_key = str(uuid.uuid4())
# → 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'

Storing and reusing keys for retries

Generate your idempotency key before making the request, and persist it alongside the logical action in your own system. This way, if the request fails at any point — timeout, network error, server error — you can safely retry with the same key.

// Recommended pattern
const idempotencyKey = randomUUID();
await db.savePayoutAttempt({ payoutId, idempotencyKey });

const response = await nadapayClient.executeTransaction({
  quoteId,
  source: {
    accountId,
    amount
  },
  destination: {
    beneficiaryAccountId
  },
  idempotencyKey
});

Rules for x-idempotency-key usage

  • One key per logical action — generate a new key for each distinct operation
  • Never reuse a key with a different payload — this will return a 409 Conflict error
  • Never reuse a key with a different endpoint — keys are scoped to the URL they were first used with
  • Persist the key before sending — so you can retry safely after failures
  • Do not use sequential or predictable values — use UUIDs

What happens when idempotency is violated

ScenarioResponse
Same key, same payload, same endpoint200 OK — returns original response
Same key, different payload409 Conflict
Same key, different endpoint409 Conflict
Missing key on a create POST400 Bad Request

Idempotency and metadata references

If you store your own transaction reference in metadata, treat it as a separate reconciliation value — it is not the same as the x-idempotency-key. Both serve different purposes:

FieldPurposeScope
x-idempotency-keyPrevents duplicate API operations on retryAPI layer
metadata.referenceYour internal identifier for the transactionBusiness layer

Best practice

Use a UUID for x-idempotency-key. If you need your own business reference, store it in metadata.


What's next

Next stepLink
Make your first API callGetting Started
See request format conventionsRequest and Response Format
Handle errors safely on retryKnown Pitfalls