What an idempotency key is
An operation is idempotent when doing it twice has the same effect as doing it once. Reading a record is naturally idempotent; creating one usually is not, because a second create makes a second record. An idempotency key is the mechanism that makes a non-idempotent request safe to repeat: the client generates a value that is unique to one logical action — commonly a UUID or another high-entropy string — and sends it with the request. The server stores that key the first time it processes the request, together with the response it produced. On any later request carrying the same key, the server skips the work and replays the stored response.
The key matters because retries are unavoidable on a network. A request can succeed on the server while the response is lost on the way back, leaving the client unsure whether anything happened. Without a way to tell a retry apart from a brand-new request, the client faces a bad choice: retry and risk doing the thing twice, or don't retry and risk not doing it at all. An idempotency key removes that dilemma. The client retries freely, and the server uses the key to collapse duplicates into a single effect — the action happens exactly once even though the request may arrive several times.
Two details make the guarantee hold. The key must be tied to one specific action and reused only for retries of that same action — not recycled across unrelated requests, or two genuinely different actions would be merged into one. And the server must scope the key correctly, typically per account, so that one client's key cannot collide with another's. Get those right and the key turns an at-least-once delivery channel into at-most-once execution.
Why it matters for crypto payments
In payments, a duplicated request is not a cosmetic bug — it can move money twice. A request to send funds that is retried after a dropped connection could, without protection, release the payout a second time; a request to create a charge could bill a customer twice. Because crypto payouts are settled on-chain and cannot be clawed back, a double send is especially costly: there is no chargeback to reverse it. The whole point of an idempotency key on a money-moving endpoint is to make the integration's natural retry behaviour safe, so that a network hiccup never becomes a duplicate transfer.
This is what lets a backend retry confidently. When an API call to create a payout times out, the client genuinely does not know whether the server received it. The correct response is to retry with the same idempotency key: if the first attempt already went through, the server replays that original result and no second payout is created; if it never arrived, the retry creates the payout for the first time. Either way the outcome is one payout, and the client reaches a definite answer instead of guessing. The key is chosen by the client, not the server, precisely so the client can attach the same one to a retry.
Idempotency keys on halfin
halfin's API uses idempotency keys so that money-moving requests are safe to retry. Payouts are the clearest case. A mass payout is submitted as a fan-out to the payouts endpoint, and each line in that request carries its own idempotency_key. Because the key is per line rather than per request, a batch that is re-sent after a timeout will not pay any destination twice — each line is matched on its own key, the lines that already succeeded are recognised as duplicates and skipped, and only the lines that never landed are created. That makes resubmitting an entire batch a safe operation, which is what you want when you cannot tell how far the first attempt got.
The practical rule for an integration is to generate one stable key per logical payout line and reuse that exact key on every retry of that line, rather than minting a fresh key each attempt. A fresh key on a retry looks like a brand-new payout to the server and defeats the protection. Pair this with the other half of trustworthy delivery — verifying the HMAC signature on the webhooks halfin sends back, such as payout.completed and payout.failed, before acting on them — and an integration can both send and receive payment events without duplicating effects on either side.
- An idempotency key is a client-supplied value that makes a request safe to retry: re-sending the same key returns the original result instead of repeating the action.
- It converts an at-least-once retry channel into at-most-once execution, so a dropped response or a re-queued job never causes a duplicate.
- Use one stable key per logical action and reuse it on every retry of that action — a fresh key on a retry looks like a new request and defeats the protection.
- halfin scopes the key per payout line: re-sending a mass payout skips lines that already succeeded and creates only the ones that never landed.