Idempotency and retries
Every write requires an Idempotency-Key header. Without one the call is
refused with missing_idempotency_key (400). This applies to create, link,
session and cancel.
Idempotency-Key: 1e6f6a1c-2c4e-4d7e-9d0e-7a1f5c2b8f3a
🔁 What happens on a retry
| What you do | What happens |
|---|---|
| Retry with the same key and the same body | The original result, no second payout |
| Retry a key whose first attempt was rejected | The same error again — even after the cause is fixed. Use a new key |
| Same key, different body | idempotency_key_conflict (409) |
| Retry while the first attempt is still running | 409 attempt_unresolved with operation_status: pending — retry it, do not treat as a failure |
New key, an existing merchant_reference, same amount, delivery and recipient | The existing payout, no second payout |
New key, an existing merchant_reference, different details | merchant_reference_conflict (409), with the existing payout in detail.payout_id |
🧩 Two separate ideas — you need both
Idempotency-Key— identifies one attempt. Change it only when you intend a new payout.merchant_reference— your own id for the payout. Permanently unique per account, so it is what you use to find the payout again later (see List payouts). It is also a second guard: even with a new key, the same reference never produces a second payout.
🚫 After a rejection
A rejected create is final for its key. Retrying the same key replays the same
error — it does not re-check your balance or limits. When the cause is fixed
(balance topped up, the hourly limit reset), send the request again with a new
Idempotency-Key. Branch on the error code, not on the HTTP status.
⏳ attempt_unresolved
{
"operation_status": "pending",
"payout_id": null,
"error": "attempt_unresolved",
"message": "the outcome is not established; retry this attempt with the same Idempotency-Key"
}
This one 409 is the exception to "do not retry a 409": the outcome is not yet established. Cancel can return it too, when the account was busy with another change at that moment. Retry the same call with the same key until you get a definite answer — it can never create a second payout.
Until you get a definite answer, do not treat attempt_unresolved as a failure.
Doing so would release a hold on a payout that may well exist.
🔗 Replays and payout_url
A retry of the same key replays the same payout with payout_url: null — a
replay must not mint a fresh secret. Store the link when you first receive it, or
fetch it again with Get or rotate claim link.