Idempotency
The BLOX endpoints that create a payout, a transfer, a withdrawal, a checkout, or a beneficiary take an Idempotency-Key header. It exists so that a request you never saw the answer to — a timeout, a dropped connection, a crashed process — can be retried without moving money twice.
This page is the shared contract. Endpoint pages link here rather than repeat it.
The header
| Name | Idempotency-Key |
| Format | A UUID v4, validated strictly — the version and variant nibbles must be right |
| Required on | POST /v1/payouts, POST /v1/payouts/beneficiaries, POST /v1/wallet/token/withdrawals, POST /v1/wallet/fiat/withdrawals, POST /v1/checkout |
| Missing | 400 — Missing idempotency key |
| Malformed | 400 — Invalid idempotency key |
Generate the key and persist it before you send the request, next to the order or transfer it belongs to. A key generated in memory is lost by the crash you most need it for.
import { randomUUID } from "node:crypto";
// Write this to your database BEFORE the API call, not after.
const idempotencyKey = randomUUID();
await db.orders.update(orderId, { payoutIdempotencyKey: idempotencyKey });What a key is scoped to
A key identifies one request, not one value. The stored record is keyed on your account plus the key, and it remembers a hash of the method, path, and canonical body.
- The same key on a different endpoint is a different record — keys do not collide across routes.
- The same key with a different body on the same route is
422. A key is bound to the exact request it first saw. - Two accounts can use the same key without seeing each other's responses.
Response statuses
| Status | Meaning | What to do |
|---|---|---|
200 | Done. Either your request just completed, or this is a replay of the original response | Use the body. Creates return 200, never 201 |
202 | The first request with this key is still running | Wait, then retry with the same key |
400 | Key missing or not a UUID v4 | Fix the header |
422 | This key was already used with a different body | You reused a key by mistake. Look up what the key already created |
A 202 body is { "message": "This request is currently processing." } — there is no resource in it. Poll with the same key until you get a 200.
If you fire two identical requests at once, the second one does not fail. It waits for the first to finish — up to 30 seconds, backing off from 100ms to 1s — and then returns the same 200. Only if the first is still running after that do you get a 202.
How long a key is remembered
24 hours. Within that window the same key always returns the original outcome. After it, the record is gone and the same key is treated as new — which is why keys should be fresh per logical operation, not recycled.
Client errors are remembered too. A 400 or 422 replays as the same rejection for the full 24 hours, so fix the request and use a new key.
When a request fails
If you never got an answer — a timeout, a dropped connection, a server error — retry with the same key. Money-moving endpoints will not act twice on one key:
| Endpoint | Retry with the same key |
|---|---|
POST /v1/payouts | Returns the original payout. Never sends a second transfer |
POST /v1/wallet/token/withdrawals | Returns the original transfer |
POST /v1/wallet/fiat/withdrawals | Returns the original withdrawal |
POST /v1/payouts/beneficiaries | Returns the existing beneficiary |
Put your order number in the checkout title — it is the merchant-supplied handle the list echoes back, so you can see whether your request already landed:
const existing = await fetch(
"https://api.blox.my/v1/checkout",
{ headers: { "blox-api-key": process.env.BLOX_API_KEY } },
).then((response) => response.json());
const alreadyLanded = existing.data.some(
(checkout) => checkout.title === "Order ORDER-2026-0001",
);
if (!alreadyLanded) {
// Safe to retry now.
}A rule of thumb
- No answer at all → same key, retry.
4xx→ fix the request, use a new key.429→ same key, after a backoff. The request never ran.202→ same key, after a short wait.