Skip to content
LogoLogo

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

NameIdempotency-Key
FormatA UUID v4, validated strictly — the version and variant nibbles must be right
Required onPOST /v1/payouts, POST /v1/payouts/beneficiaries, POST /v1/wallet/token/withdrawals, POST /v1/wallet/fiat/withdrawals, POST /v1/checkout
Missing400Missing idempotency key
Malformed400Invalid 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

StatusMeaningWhat to do
200Done. Either your request just completed, or this is a replay of the original responseUse the body. Creates return 200, never 201
202The first request with this key is still runningWait, then retry with the same key
400Key missing or not a UUID v4Fix the header
422This key was already used with a different bodyYou 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:

EndpointRetry with the same key
POST /v1/payoutsReturns the original payout. Never sends a second transfer
POST /v1/wallet/token/withdrawalsReturns the original transfer
POST /v1/wallet/fiat/withdrawalsReturns the original withdrawal
POST /v1/payouts/beneficiariesReturns 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.