Networks fail in the worst possible way: your request arrives, we process it, and the response is lost on the way back. You don’t know whether the payment went out. Retry and you might send it twice; don’t retry and you might not have sent it at all. An idempotency key removes that ambiguity. You pick a key, send it with the request, and reuse the same key on every retry of that same operation. We guarantee the operation happens at most once — a retry returns the original result instead of creating a second payment, payee, or card.
Optional today, required later. The Idempotency-Key header is optional right now: requests without one behave exactly as they always have. We intend to enforce it on all money-movement and resource-creating endpoints in a future release, so requests without a key will start being rejected.Send a key on every create from now on. Adding it later is a migration; adding it today is one line, and it protects you against duplicates in the meantime.

How to send it

Pass the key in the Idempotency-Key request header. It must be a UUID.
Every endpoint in the table below also accepts the key as an idempotencyKey field in the JSON body, for parity with batch payments. If you send both, the header wins. Prefer the header — it’s the same on every endpoint and it doesn’t interact with the request body.
Generate one key per logical operation, not per attempt. Mint the UUID when the user taps “Send”, store it alongside the operation, and reuse it for every retry — including retries after a timeout, a 5xx, or an app restart. A fresh key on each attempt gives you no protection at all.

Where it’s supported

Read-only endpoints (GET) and endpoints that change existing state (block, cancel, activate) don’t take a key: repeating them doesn’t create anything.
Card load/unload is not on this list. It accepts an Idempotency-Key, but only uses it to correlate the balance event — it does not deduplicate, so a retry moves the money again. Confirm the result with card transactions before retrying a load or unload.

What happens on a retry

409 IDEMPOTENT_REQUEST_IN_PROGRESS and 409 IDEMPOTENCY_KEY_CONFLICT apply to payments and cards. Adding a beneficiary is a single synchronous write, so it has no in-progress state.
A batch key is not a replay key. Unlike the single-resource creates, submitting the same batch twice with the same idempotencyKey does not return the first batch — it re-runs the submission against the same batchId, and the payments can go out a second time. Parameters aren’t compared either, so a reused key with a different list of payments is not rejected.Treat submit as a one-shot call: if a response is lost, track the batch to find out whether it landed. Never resend.

Scope of a key

On the single-resource creates (payments, beneficiaries, cards), keys are scoped to the authenticated customer, so two customers can pick the same UUID without colliding.
Batch payments are the exception. A batch’s identity is derived from the idempotencyKey alone, not from the customer, so the key must be unique across your whole integration — not just per customer. Generate a fresh UUID per submission (never a constant, a template value, or a reused test fixture): two submissions that share a key land on the same batchId and re-run the submission, which can pay the batch out twice.
A key never weakens the first request’s step-up. When you add a payee, the confirmation is verified before the replay is resolved, so a retry still has to prove it’s the customer.On payments, a replay short-circuits earlier: once a payment has been dispatched under a key, a retry with that key returns the original result without re-running the step-up. The original payment was fully authorised — the replay just hands you back its result, so a lost response doesn’t cost your customer a second confirmation prompt.

Putting it together

The two rules that make this work: the same key and the same parameters on every attempt. Change either and you’re making a new request, not retrying the old one.