Payout API
Send Malaysian bank payouts from a prefunded BLOX balance. This guide follows the production integration sequence from account setup to reconciliation.
Prerequisites
Before you integrate, prepare:
| Requirement | How to get it |
|---|---|
| Payout API access | Ask BLOX to enable Payout on your sandbox account |
| API key and signer key id | Ask BLOX for API access |
| Ed25519 key pair | Generate it yourself; register only the public key with BLOX |
| Public HTTPS webhook URL | Register a PAYOUT webhook under Devtools → Webhooks |
| Prefund balance | Create a top-up under Payouts → Top up |
Use the sandbox base URL while integrating:
https://api.sandbox.blox.myProduction uses https://api.blox.my. All read requests require blox-api-key; all write requests also require an HTTP message signature. Payout signatures accept a created= timestamp within ±300 seconds.
How it works
A payout moves through one of these paths:
INITIATED ─────► SETTLED ─────► RETURNED
│
└──────────► REVERSED| Status | Meaning |
|---|---|
INITIATED | Accepted, debited from prefund, and queued for the bank |
SETTLED | The bank confirmed delivery |
REVERSED | Rejected before settlement; netAmount and the fee return |
RETURNED | Returned after settlement; netAmount returns and the fee is kept |
Amounts are always in sen: integers in requests and strings in responses. Read fee and netAmount from the response instead of calculating them yourself.
Every payout requires a feeMode:
DEDUCT_FROM_AMOUNT: the beneficiary receivesamount - fee.CHARGE_TO_PREFUND: the beneficiary receives the fullamount; the fee is debited separately.
In both cases, the prefund debit is netAmount + fee.
BLOX configures the fee per account as a percentage, a fixed amount, and one formula. MAX uses the larger charge (max(amount × rate, fixed)); SUM adds them. The standard arrangement is MAX at 1.00% with a RM1.50 floor, but confirm your configuration with BLOX. The percentage is rounded half-up to the nearest sen before the formula is applied; the response remains authoritative.
Step-by-step guide
Step 1: Configure your application
Generate an Ed25519 key pair and send only the public key to BLOX:
openssl genpkey -algorithm Ed25519 -out blox_private_key.pem
openssl pkey -in blox_private_key.pem -pubout -out blox_public_key.pemSet your sandbox credentials. The examples below assume you have copied the Node.js signBlox or Python sign_blox helper from Request Signing.
Keep the private key on your server. Never send it to BLOX or expose it in frontend code.
Step 2: Register your webhook and fund the prefund
In the dashboard:
- Register a public HTTPS endpoint of type
PAYOUTunder Devtools → Webhooks. - Create a sandbox top-up under Payouts → Top up.
- Wait for
payout.prefund_completed; sandbox top-ups normally complete in about 10 seconds.
Verify webhook signatures and process each eventId once. See Webhook Events for the payloads.
Step 3: Check the available balance
Read available before creating a payout. Do not subtract inFlight; it is already excluded.
See GET /v1/payout/prefund/balance for every response field.
Step 4: Choose the beneficiary
Fetch the active banks and use the returned bankCode. Then choose one payout path:
- Send
beneficiaryinline for a new or one-off recipient. - Register the recipient first and send its
beneficiaryIdwhen you want to reuse it.
See Active Banks and Beneficiary endpoints for the complete fields.
Step 5: Create the payout
Generate and persist one UUID v4 Idempotency-Key before sending. Reuse the same key after a timeout or 5xx; a new key can create another transfer.
A successful create returns INITIATED. A 202 means the first request is still processing; retry with the same idempotency key. See POST /v1/payouts for validation, response fields, and retry rules.
Step 6: Handle the final outcome
Use payout.updated webhooks as the primary status signal. Store the payoutId, process webhook eventId values idempotently, and handle all three final statuses: SETTLED, REVERSED, and RETURNED.
Use the read endpoint only to reconcile a missed webhook:
Step 7 (optional): Pay a beneficiary from a deposit
Ask BLOX to enable paying a beneficiary from a deposit, then give a beneficiary its own on-chain address. Tokens sent to that address are converted and paid to that beneficiary — no POST /v1/payouts, and no prefund balance involved.
Set it up once per beneficiary:
POST /v1/payouts/beneficiaries/{beneficiaryId}/addressreturns the address. A beneficiary has one address for its lifetime, so calling again returns the same one.POST /v1/payouts/beneficiaries/{beneficiaryId}/address/whitelistfor each address you will send from.
The second step is not optional. A trigger address accepts nothing until you allow a sender. Deposits from any other address are refused with sender_not_allowed, and the tokens simply stay in your wallet.
Transfers made this way are reported on payout.deposit.updated and payout.withdrawal.updated, not payout.created or payout.updated, and they do not appear in GET /v1/payouts. One deposit produces at most one transfer.
If a deposit is refused for a reason you can fix, fix it and call POST /v1/payouts/deposits/{id}/redrive. If you sent tokens and heard nothing at all, POST /v1/payouts/deposits/check-missed with the transaction hash is the way back.
Before switching to production, test settlement, reversal, return, and pending outcomes using Sandbox Testing. Then change the base URL and credentials, fund the production prefund, confirm your fee and account limits with BLOX, and alert on RETURNED and payout.prefund_reversed.