Payments
Idempotency and safe retries
Last updated 14 September 2026
A payment request that times out is the worst kind of failure: you do not know whether it happened. Idempotency keys turn that into a safe retry.
How the key works
Every POST /payments must carry an x-idempotent-key header: a UUID v4 that you generate. If Xace receives a second request with the same key, it does not create a second payment. The key identifies the request, so a retry with the same key and the same body is safe.
Generating and storing keys
The key must be created before the first attempt and stored with the thing it represents, so a retry after a crash can find it again.
async function submitPayout(payout) {
// payout.idempotencyKey is created when the payout row is created,
// not when the request is sent.
if (!payout.idempotencyKey) {
payout.idempotencyKey = crypto.randomUUID();
await db.save(payout);
}
const res = await fetch('https://customer-api.prod.xace.io/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'x-idempotent-key': payout.idempotencyKey,
},
body: JSON.stringify(payout.body),
});
if (res.status === 202) {
const data = await res.json();
await db.markSubmitted(payout.id, data.submitted[0]?.xtid, data.requestId);
}
return res;
}Rules:
- One key per logical payment. If the amount or payee changes, that is a new payment and a new key.
- The same key must always be sent with the same body.
- Keys are UUID v4. Do not derive them from the invoice number; a re-issued invoice would collide.
After a timeout
- Retry with the same key and body. Usually this simply returns the original result.
- If the retry also fails, do not generate a new key. Look the payment up instead:
GET /account/{xaid}/approvalsfor anything awaiting approval, andGET /account/{xaid}/transactionsfor anything already submitted, matching onreference. - Only if neither shows the payment, and the retries are exhausted, raise it to a human. Do not let the job decide to send again with a fresh key.
A new key is a new payment
Every duplicate payout incident in an integration comes from a retry with a fresh key. Store the key first; reuse it always.
Batches
An array body uses one key for the whole batch. Retrying the batch with the same key is safe. If you need to resubmit only the rejected lines from a batch, that is a new request with a new key and a body containing only those lines.