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
| Method | Send x-idempotency-key? |
|---|---|
POST (create) | ✅ Always |
PATCH (update) | ❌ No |
GET | ❌ No |
DELETE | ❌ No |
Sending anx-idempotency-keyon aGETrequest will return a400 Bad Requesterror.
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 Conflicterror - 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
| Scenario | Response |
|---|---|
| Same key, same payload, same endpoint | 200 OK — returns original response |
| Same key, different payload | 409 Conflict |
| Same key, different endpoint | 409 Conflict |
| Missing key on a create POST | 400 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:
| Field | Purpose | Scope |
|---|---|---|
x-idempotency-key | Prevents duplicate API operations on retry | API layer |
metadata.reference | Your internal identifier for the transaction | Business layer |
Best practiceUse a UUID for
x-idempotency-key. If you need your own business reference, store it inmetadata.
What's next
| Next step | Link |
|---|---|
| Make your first API call | Getting Started |
| See request format conventions | Request and Response Format |
| Handle errors safely on retry | Known Pitfalls |
Updated about 1 month ago