Payout API Reference
This page shows all Payout API endpoints in the order of integration. Use https://api.sandbox.blox.my for tests. Use https://api.blox.my in production.
Read requests must include the blox-api-key header. Write requests must include the API key and an HTTP message signature. Some write requests must also include an Idempotency-Key UUID v4. The section for each endpoint tells you if it is necessary.
All errors use the format { "code": "…", "message": "…", "requestId": "…" }. Use the code value in your error logic. Do not use the message value. The tables under each endpoint show the expected combinations of status and code.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /v1/payout/prefund/balance | Get the available and pending balances |
GET | /v1/payouts/active-banks | Get the bank code for a beneficiary |
POST | /v1/payouts/beneficiaries | Register a reusable beneficiary |
GET | /v1/payouts/beneficiaries | List active beneficiaries |
GET | /v1/payouts/beneficiaries/{beneficiaryId} | Get one beneficiary |
DELETE | /v1/payouts/beneficiaries/{beneficiaryId} | Deactivate a beneficiary |
POST | /v1/payouts | Create and send a payout |
GET | /v1/payouts/{id} | Get a payout for reconciliation |
To pay a beneficiary from a deposit of tokens to its trigger address, refer to Onchain Trigger.
Get prefund balance
/v1/payout/prefund/balanceAPI keyReturns the prefund balance. This endpoint has no parameters. The limit is 60 requests per minute for each account.
Response — 200
{
"available": "4900000",
"inFlight": "99000",
"receivable": "0"
}| Field | Type | Description |
|---|---|---|
available | string | Amount in sen that you can use for payouts |
inFlight | string | The netAmount of accepted payouts that do not have a final status |
receivable | string | Usually "0". A negative value is an amount that you owe to BLOX after a prefund reversal that your balance did not cover |
The available value does not include inFlight. Do not subtract inFlight from available. If receivable is negative, the payout endpoints return 403 FEATURE_DISABLED until the negative amount is resolved. To get top-up notifications, use the payout.prefund_completed webhook event. Do not poll this endpoint for top-ups.
Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | The API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
429 | RATE_LIMITED | The endpoint or account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Get active banks
/v1/payouts/active-banksAPI keyReturns the banks and e-wallets that can receive payouts. Each item has the bank code that beneficiary and payout requests accept. The limit is 600 requests per minute for each account.
Response — 200
[
{
"name": "Maybank",
"bankCode": "MBBEMYKL"
},
{
"name": "TNG Digital (TouchNGo)",
"bankCode": "TNGDMYNB"
}
]| Field | Type | Description |
|---|---|---|
name | string | The name of the bank or e-wallet |
bankCode | string | The BIC of the bank |
Use this bankCode value in all request fields with the name bankCode.
Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | The API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
429 | RATE_LIMITED | The endpoint or account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Create beneficiary
/v1/payouts/beneficiariesAPI key + signatureRegisters a beneficiary that you can use for more than one payout. Requests to this endpoint must include an Idempotency-Key UUID v4. The limit is 20 requests per minute for each account.
Identify the beneficiary in one of two ways: a bank account or a DuitNow proxy. Send the fields for one of the two types only. Do not send fields that are not in the table for that type.
A bank account:
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | Yes | 2–96 characters |
bankCode | string | Yes | 1–11 characters. Use a code from /v1/payouts/active-banks |
accountNumber | string | Yes | 3–20 digits |
nameCheck | string | No | strict or loose. Refer to Name check below |
A DuitNow proxy: an identifier that the beneficiary registered with PayNet. At payment time, PayNet finds the account for the identifier. PayNet also supplies the bank at that time. Thus, you register a proxy with only the identifier.
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | Yes | 2–96 characters |
proxyType | string | Yes | NRIC, PSPT, BREG, MBNO, or ARMN |
proxyValue | string | Yes | 3–40 characters, in the format for its proxyType. Refer to the table below |
countryCode | string | Only for PSPT | ALPHA-3 code of the country of issue, 3 letters, for example MYS |
nameCheck | string | No | strict or loose. Refer to Name check below |
proxyType | Identifier | proxyValue format |
|---|---|---|
MBNO | Mobile number | Digits only, with the country dialing code and without a +. 7–19 digits. Example: 60123456789 |
NRIC | Malaysian identity card number | Letters and digits only. Example: 900101015432 |
PSPT | Passport number. Also send countryCode | Letters and digits only. Example: E39412345 |
BREG | Business registration number | Letters and digits only. Example: 202201001234 |
ARMN | Army number | Letters and digits only |
Send the identifier as the beneficiary registered it with PayNet. Remove all hyphens, spaces, and punctuation. For PayNet, 900101-01-5432 and 900101015432 are two different proxies. PayNet finds an account only for 900101015432.
DuitNow proxies are not available on all accounts. If your account does not support proxies, a proxy beneficiary gets 400 UNKNOWN_BENEFICIARY. Ask BLOX if your account supports proxies.
BLOX asks the bank if the account exists. If the bank does not recognize the account, the response is 400 INVALID_REQUEST. If BLOX cannot connect to the bank, the response is 503 SERVICE_UNAVAILABLE. In that case, BLOX does not register the beneficiary.
Name check. You can send nameCheck in this request or in the inline beneficiary object of a payout. To check a registered beneficiary, use Verify beneficiary. If you send nameCheck, BLOX compares the name that you send with the name of the account holder:
nameCheck | Passes when | Otherwise |
|---|---|---|
| omitted | The name result does not stop the registration | — |
strict | The names are the same but spelled differently. Accepted differences: case, spacing, punctuation, and accents (ACME SDN. BHD. = ACME SDN BHD, Dae'navan A/L Thaiveegan = Daenavan AL Thaiveegan), a title that the bank adds (ENCIK ..., ...; MR), BIN/BINTI on one side only, and a long name that the bank field truncated. BIN does not match BINTI. A/L does not match A/P. Use strict if you have the name as it is on the IC of the beneficiary | 400 BENEFICIARY_NAME_MISMATCH. If the name passes at loose, the message tells you |
loose | All differences that strict accepts. Also the same name in a different order or in part: different word breaks (LIM CHEONGKENG = LIM CHEONG KENG), the surname at the other end (Joey Ng = Ng Joey), one holder of a joint account, one side of an @ alias, or a name of two or more words that is in the bank name (Jordan Teo = JORDAN TEO ZHI CHING, Kenny Liew = KENNY LIEW LI CHENG). One word alone does not match. Use loose if you know the name of the beneficiary, but possibly not as it is on the IC | 400 BENEFICIARY_NAME_MISMATCH |
Sometimes the bank cannot give a result for the name. For example, the bank does not support the name check, or the bank does not return a holder name. In this case, a request with nameCheck fails with 400 NAME_CHECK_UNAVAILABLE. To register the beneficiary without verification, send the request again without nameCheck. Use a new Idempotency-Key.
Example of a bank beneficiary with a name check:
{ "name": "Ada Lovelace", "bankCode": "MBBM", "accountNumber": "1234567890", "nameCheck": "loose" }Result for each bank response:
| Bank result | nameCheck omitted | loose | strict |
|---|---|---|---|
| Account invalid, dormant, closed, or blocked | 400 INVALID_REQUEST | same | same |
| BLOX cannot connect to the bank | 503 SERVICE_UNAVAILABLE. Retry with the same Idempotency-Key | same | same |
| The bank does not return a holder name | Registered, bankAccountVerified: false | 400 NAME_CHECK_UNAVAILABLE | same |
Name matches at strict | Registered, bankAccountNameMatch: "strict" | same | same |
Name matches at loose only | Registered, bankAccountNameMatch: "loose" | same | 400 BENEFICIARY_NAME_MISMATCH. The message tells you that the name passes at loose |
| Name does not match | Registered, bankAccountVerified: false | 400 BENEFICIARY_NAME_MISMATCH | same |
BLOX does not return the holder name from the bank. If a name fails at strict but passes at loose, send the request again with a new Idempotency-Key. In that request, send the full name of the beneficiary as it is on the IC, or send nameCheck: "loose".
Result of a 400 response:
- Registration: BLOX does not register the beneficiary.
- Verify beneficiary:
bankAccountVerifieddoes not change. You can still pay the beneficiary. - A payout with an inline
beneficiary: BLOX rejects the payout and does not debit the prefund balance.
BLOX removes spaces at the start and end of the name and changes the name to uppercase. If you register the same destination with the same name again, BLOX returns the existing beneficiary. The name comparison is not case-sensitive. BLOX does not ask the bank again, except for a nameCheck at a level that the beneficiary did not pass before. A repeat registration does not count toward the limit of active beneficiaries. The default limit is 10,000.
Response — 200
{
"id": "7c1a3e88-9d2b-4f61-8a44-1f0b6d3c9a55",
"name": "ADA LOVELACE",
"accountNumber": "1234567890",
"bank": { "code": "MBBM", "name": "Maybank" },
"proxy": null,
"autoWithdrawalAddress": null,
"bankAccountVerified": true,
"bankAccountVerifiedAt": "2026-07-16T09:30:00.000Z",
"bankAccountNameMatch": "strict",
"createdAt": "2026-07-16T09:30:00.000Z",
"updatedAt": "2026-07-16T09:30:00.000Z"
}For a DuitNow proxy beneficiary, the response has proxy set, and accountNumber and bank are null:
{
"id": "0b9d7c21-4e83-4a17-9f52-6c8e1a2d3b40",
"name": "ADA LOVELACE",
"accountNumber": null,
"bank": null,
"proxy": { "type": "MBNO", "value": "60123456789", "countryCode": null },
"autoWithdrawalAddress": null,
"bankAccountVerified": true,
"bankAccountVerifiedAt": "2026-07-16T09:30:00.000Z",
"bankAccountNameMatch": "strict",
"createdAt": "2026-07-16T09:30:00.000Z",
"updatedAt": "2026-07-16T09:30:00.000Z"
}The response uses the shared Beneficiary object.
Error responses
| Status | code | When |
|---|---|---|
400 | VALIDATION_FAILED | A body field is missing, malformed, or outside its constraints |
400 | INVALID_REQUEST | The Idempotency-Key is missing or malformed. Also returned when the bank rejects the account |
400 | UNKNOWN_BENEFICIARY | The bankCode is not in your /v1/payouts/active-banks list. Also returned for a DuitNow proxy if your account does not support proxies |
400 | BENEFICIARY_NAME_MISMATCH | Only with nameCheck. The name does not match the bank records at the requested level |
400 | NAME_CHECK_UNAVAILABLE | Only with nameCheck. The bank cannot give a name result for this account. Retry without nameCheck |
401 | UNAUTHORIZED | The API key or signature is missing or invalid |
403 | LIMIT_EXCEEDED | You have the maximum number of active beneficiaries |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
422 | CONFLICT | You used the idempotency key before with a different body |
429 | RATE_LIMITED | The endpoint or account rate limit is exceeded |
503 | SERVICE_UNAVAILABLE | The bank could not confirm the beneficiary at this time. BLOX did not register the beneficiary. Retry with the same Idempotency-Key |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
400 VALIDATION_FAILED
For a request with name: "A":
{
"code": "VALIDATION_FAILED",
"message": "name: Beneficiary name can't be less than 2 characters",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 INVALID_REQUEST
{
"code": "INVALID_REQUEST",
"message": "Beneficiary rejected by the bank — the account may be invalid, dormant, closed, or blocked",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}For a missing or malformed Idempotency-Key:
{
"code": "INVALID_REQUEST",
"message": "Idempotency-Key must be a valid UUID v4",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 BENEFICIARY_NAME_MISMATCH
For strict with name: "Jordan Teo", if the bank has JORDAN TEO ZHI CHING:
{
"code": "BENEFICIARY_NAME_MISMATCH",
"message": "Beneficiary name did not match under strict comparison, but would under loose. Use the full name exactly as registered with the bank, or retry with nameCheck \"loose\".",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}For all other mismatches:
{
"code": "BENEFICIARY_NAME_MISMATCH",
"message": "Beneficiary name does not match the bank's records. Use the name exactly as registered with the bank.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 NAME_CHECK_UNAVAILABLE
{
"code": "NAME_CHECK_UNAVAILABLE",
"message": "The bank did not return a holder name for this account, so the name cannot be checked. Retry without nameCheck to register the beneficiary unverified.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 UNKNOWN_BENEFICIARY
{
"code": "UNKNOWN_BENEFICIARY",
"message": "Unknown bank code",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 LIMIT_EXCEEDED
{
"code": "LIMIT_EXCEEDED",
"message": "Active beneficiary limit exceeded",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 FEATURE_DISABLED
{
"code": "FEATURE_DISABLED",
"message": "Payout is not enabled for this account",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 ACCOUNT_TERMINATED
{
"code": "ACCOUNT_TERMINATED",
"message": "Account is terminated",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}422 CONFLICT
{
"code": "CONFLICT",
"message": "Idempotency key was already used with a different request body",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}429 RATE_LIMITED
{
"code": "RATE_LIMITED",
"message": "Too many beneficiary creations. Please try again later.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}503 SERVICE_UNAVAILABLE
{
"code": "SERVICE_UNAVAILABLE",
"message": "Could not verify the beneficiary with the bank right now. Nothing was submitted — please retry shortly.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}List beneficiaries
/v1/payouts/beneficiariesAPI keyReturns the active beneficiaries. The newest beneficiary is first.
| Query | Type | Description |
|---|---|---|
limit | integer | 1–100. The default is 25 |
cursor | string | The nextCursor value from the previous response. Treat it as an opaque string |
search | string | Finds beneficiaries where the name, account number, bank name, or auto-withdrawal address contains this text. The match is not case-sensitive |
Response — 200
{
"data": [],
"hasMore": true,
"nextCursor": "9f21ab04-…"
}Each data item is a Beneficiary object. To get the next page, send nextCursor as cursor. Do this until hasMore is false. On the last page, nextCursor is null.
Error responses
| Status | code | When |
|---|---|---|
400 | VALIDATION_FAILED | A query parameter is malformed or outside its constraints |
401 | UNAUTHORIZED | The API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
429 | RATE_LIMITED | The account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
400 VALIDATION_FAILED
For ?limit=101:
{
"code": "VALIDATION_FAILED",
"message": "limit: Number must be less than or equal to 100",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Get beneficiary
/v1/payouts/beneficiaries/{beneficiaryId}API keyReturns one active beneficiary. If the ID is unknown, inactive, or belongs to a different account, the response is 404 NOT_FOUND.
| Path parameter | Type | Description |
|---|---|---|
beneficiaryId | string (UUID) | The ID of a beneficiary of your account |
Response — 200: Beneficiary object.
Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | The API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
404 | NOT_FOUND | The beneficiary is unknown, inactive, or belongs to a different account |
429 | RATE_LIMITED | The account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Delete beneficiary
/v1/payouts/beneficiaries/{beneficiaryId}API key + signatureDeactivates the beneficiary and its trigger address, if it has one. Sign only @method and @path. A Content-Digest is not necessary.
Response — 200
{ "success": true }Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | The API key or signature is missing or invalid |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
404 | NOT_FOUND | The beneficiary is unknown, inactive, or belongs to a different account |
429 | RATE_LIMITED | The account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Verify beneficiary
/v1/payouts/beneficiaries/{id}/verifyAPI key + signatureChecks the holder name of a registered beneficiary again, at the level that you select. The limit is 20 requests per minute for each account.
| Field | Type | Required | Constraints |
|---|---|---|---|
nameCheck | string | Yes | strict or loose. The rules are the same as in Create beneficiary |
Returns the Beneficiary object. If the response is BENEFICIARY_NAME_MISMATCH, the verified status of the beneficiary does not change. You can still pay the beneficiary.
Response — 200
The Beneficiary object. The bankAccountVerified, bankAccountVerifiedAt, and bankAccountNameMatch fields show the result of this check.
Error responses
| Status | code | When |
|---|---|---|
400 | VALIDATION_FAILED | nameCheck is missing, or it is not strict or loose |
400 | INVALID_REQUEST | The bank rejected the account |
400 | BENEFICIARY_NAME_MISMATCH | The name does not match the bank records at the requested level. The verified status does not change |
400 | NAME_CHECK_UNAVAILABLE | The bank cannot give a name result for this account |
401 | UNAUTHORIZED | The API key or signature is missing or invalid |
403 | FEATURE_DISABLED | Payouts are not enabled for the account |
403 | ACCOUNT_TERMINATED | The account is terminated |
404 | NOT_FOUND | The beneficiary is unknown, inactive, or belongs to a different account |
429 | RATE_LIMITED | You sent more than 20 verification requests in one minute |
503 | SERVICE_UNAVAILABLE | BLOX could not connect to the bank. The beneficiary did not change. Retry later |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
400 VALIDATION_FAILED
For a body without nameCheck:
{
"code": "VALIDATION_FAILED",
"message": "nameCheck: Required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 INVALID_REQUEST
{
"code": "INVALID_REQUEST",
"message": "Beneficiary rejected by the bank — the account may be invalid, dormant, closed, or blocked",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 BENEFICIARY_NAME_MISMATCH
For strict with name: "Jordan Teo", if the bank has JORDAN TEO ZHI CHING:
{
"code": "BENEFICIARY_NAME_MISMATCH",
"message": "Beneficiary name did not match under strict comparison, but would under loose. Use the full name exactly as registered with the bank, or retry with nameCheck \"loose\".",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}For all other mismatches:
{
"code": "BENEFICIARY_NAME_MISMATCH",
"message": "Beneficiary name does not match the bank's records. Use the name exactly as registered with the bank.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 NAME_CHECK_UNAVAILABLE
{
"code": "NAME_CHECK_UNAVAILABLE",
"message": "The bank did not return a holder name for this account, so the name cannot be checked. Retry without nameCheck to register the beneficiary unverified.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 FEATURE_DISABLED
{
"code": "FEATURE_DISABLED",
"message": "Payout is not enabled for this account",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 ACCOUNT_TERMINATED
{
"code": "ACCOUNT_TERMINATED",
"message": "Account is terminated",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}404 NOT_FOUND
{
"code": "NOT_FOUND",
"message": "Beneficiary not found",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}429 RATE_LIMITED
{
"code": "RATE_LIMITED",
"message": "Too many beneficiary verifications. Please try again later.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}503 SERVICE_UNAVAILABLE
{
"code": "SERVICE_UNAVAILABLE",
"message": "Could not verify the beneficiary with the bank right now. Nothing was submitted — please retry shortly.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Create payout
/v1/payoutsAPI key + signatureCreates a payout and submits it. A separate submit request is not necessary. BLOX debits an accepted payout from available. The response shows the payout with the status INITIATED. The limit is 300 requests per minute for each account.
Header
| Header | Required | Description |
|---|---|---|
Idempotency-Key | Yes | A UUID v4. Store one key for each payout before you send it. Use the same key when you retry |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
amount | integer | Yes | 100–10000000 sen |
feeMode | string | Yes | DEDUCT_FROM_AMOUNT or CHARGE_TO_PREFUND |
beneficiaryId | string (UUID) | Conditional | Send beneficiaryId or beneficiary, but not the two. A payout does not check a registered beneficiary again. To check it, use Verify beneficiary |
beneficiary | object | Conditional | The same fields as Create beneficiary |
reference | string | No | 1–20 characters, after BLOX removes spaces at the start and end. If you do not send it, BLOX generates PO + 8 characters |
With DEDUCT_FROM_AMOUNT, the beneficiary gets amount - fee. With CHARGE_TO_PREFUND, the beneficiary gets the full amount. In the two modes, BLOX debits netAmount + fee from the prefund balance. Get the two values from the response.
amount is the gross value before the fee. With DEDUCT_FROM_AMOUNT, netAmount must be RM1.00 or more. The standard minimum fee is RM1.50. Thus, the smallest gross amount that you can send is RM2.50.
The first payout to an inline beneficiary registers that beneficiary. BLOX asks the bank if the account exists, as POST /v1/payouts/beneficiaries does. If you send the same bank, account number, and name again, the payout uses the same beneficiaryId. The name comparison is not case-sensitive.
For a payout to that beneficiaryId, BLOX does not ask the bank again. The exception is a nameCheck at a level that the beneficiary did not pass before. BLOX checks the name only if you send nameCheck. If a check fails, the payout fails (400 INVALID_REQUEST, 400 BENEFICIARY_NAME_MISMATCH, 400 NAME_CHECK_UNAVAILABLE, or 503). BLOX does not debit the prefund balance.
Response — 200
{
"payoutId": "b0e6c2f4-2a1d-4a9e-9f3c-2c9f5a1b7e10",
"status": "INITIATED",
"type": "STANDARD",
"amount": "100000",
"fee": "1000",
"netAmount": "99000",
"beneficiaryId": "7c1a3e88-9d2b-4f61-8a44-1f0b6d3c9a55",
"bankAccountId": null,
"idempotencyKey": "6f9619ff-8b86-d011-b42d-00cf4fc964ff",
"reference": "INV-4471",
"statusReason": null,
"createdAt": "2026-07-16T09:30:00.000Z",
"submittedAt": null
}The response uses the shared Payout object. A 202 response tells you that the first request with that key is in progress. Retry with the same key.
Idempotency rules
- Store one UUID v4 for each payout before you send the request.
- If the request times out or returns
429,500, or503, retry with the same key. - If BLOX rejects the request with a different
4xx, correct the request. Then send it with a new key. - If you use a key again with a different body, the response is
422 CONFLICT.
Refer to Payout Errors for all codes and retry rules.
Error responses
| Status | code | When |
|---|---|---|
400 | VALIDATION_FAILED | The body is invalid. This includes a body with the two beneficiary fields or with no beneficiary field |
400 | INVALID_REQUEST | The idempotency key is invalid. Also returned if the bank rejects the account of an inline beneficiary, or if the net amount is less than RM1 |
400 | INSUFFICIENT_BALANCE | The prefund balance is less than netAmount + fee |
400 | UNKNOWN_BENEFICIARY | The beneficiary is inactive or belongs to a different account. Also returned if the bank code is not in your /v1/payouts/active-banks list, or for a DuitNow proxy if your account does not support proxies |
400 | BENEFICIARY_NAME_MISMATCH | Only with nameCheck on an inline beneficiary. The name does not match the bank records at the requested level |
400 | NAME_CHECK_UNAVAILABLE | Only with nameCheck on an inline beneficiary. The bank cannot give a name result. Retry without nameCheck |
400 | LIMIT_EXCEEDED | The configured daily or monthly payout limit is reached |
401 | UNAUTHORIZED | The API key or signature is missing or invalid |
403 | LIMIT_EXCEEDED | A new inline beneficiary would make the number of active beneficiaries more than the limit |
403 | FEATURE_DISABLED | Payouts are disabled. Or, payouts are suspended because of a prefund reversal that the balance did not cover |
403 | ACCOUNT_FROZEN / ACCOUNT_PENDING_VERIFICATION / ACCOUNT_TERMINATED | The account status does not allow payouts |
422 | CONFLICT | You used the idempotency key before with a different body |
429 | RATE_LIMITED | The endpoint or account rate limit is exceeded |
503 | SERVICE_UNAVAILABLE | BLOX paused payout creation, or the bank could not confirm an inline beneficiary at this time. BLOX did not create a payout or debit the prefund balance |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
400 VALIDATION_FAILED
For a request with amount: 99:
{
"code": "VALIDATION_FAILED",
"message": "amount: Amount can't be less than 1 MYR",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 INVALID_REQUEST
{
"code": "INVALID_REQUEST",
"message": "Idempotency-Key must be a valid UUID v4",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}For an inline beneficiary that the bank rejected:
{
"code": "INVALID_REQUEST",
"message": "Beneficiary rejected by the bank — the account may be invalid, dormant, closed, or blocked",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 INSUFFICIENT_BALANCE
{
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient prefund balance",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 UNKNOWN_BENEFICIARY
{
"code": "UNKNOWN_BENEFICIARY",
"message": "Beneficiary not found",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 BENEFICIARY_NAME_MISMATCH
{
"code": "BENEFICIARY_NAME_MISMATCH",
"message": "Beneficiary name does not match the bank's records. Use the name exactly as registered with the bank.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 NAME_CHECK_UNAVAILABLE
{
"code": "NAME_CHECK_UNAVAILABLE",
"message": "The bank did not return a holder name for this account, so the name cannot be checked. Retry without nameCheck to register the beneficiary unverified.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}400 LIMIT_EXCEEDED
{
"code": "LIMIT_EXCEEDED",
"message": "Daily payout limit exceeded",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 LIMIT_EXCEEDED
{
"code": "LIMIT_EXCEEDED",
"message": "Active beneficiary limit exceeded",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 FEATURE_DISABLED
{
"code": "FEATURE_DISABLED",
"message": "Payout is not enabled for this account",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 ACCOUNT_FROZEN
{
"code": "ACCOUNT_FROZEN",
"message": "Account is frozen",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 ACCOUNT_PENDING_VERIFICATION
{
"code": "ACCOUNT_PENDING_VERIFICATION",
"message": "Account is pending verification",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}403 ACCOUNT_TERMINATED
{
"code": "ACCOUNT_TERMINATED",
"message": "Account is terminated",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}422 CONFLICT
{
"code": "CONFLICT",
"message": "Idempotency key was already used with a different request body",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}429 RATE_LIMITED
{
"code": "RATE_LIMITED",
"message": "Too many payout requests. Please try again later.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}503 SERVICE_UNAVAILABLE
{
"code": "SERVICE_UNAVAILABLE",
"message": "Payout service is temporarily unavailable. Please try again later.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}For an inline beneficiary when BLOX cannot connect to the bank:
{
"code": "SERVICE_UNAVAILABLE",
"message": "Could not verify the beneficiary with the bank right now. Nothing was submitted — please retry shortly.",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Get payout
/v1/payouts/{id}API keyReturns the payout with the given payoutId. To get status changes, use webhook events. Use this endpoint for reconciliation, or if you did not receive a webhook event. The limit is 600 requests per minute for each account.
| Path parameter | Type | Description |
|---|---|---|
id | string (UUID) | The ID of a payout of your account |
Response — 200: Payout object. If the ID is unknown or belongs to a different account, the response is 404 NOT_FOUND.
Error responses
| Status | code | When |
|---|---|---|
401 | UNAUTHORIZED | The API key is missing, unknown, or inactive |
403 | FEATURE_DISABLED | Payouts are disabled. This includes a suspension because of a prefund reversal that the balance did not cover |
403 | ACCOUNT_TERMINATED | The account is terminated |
404 | NOT_FOUND | The payout is unknown or belongs to a different account |
429 | RATE_LIMITED | The endpoint or account rate limit is exceeded |
5xx | INTERNAL_ERROR | An unexpected error occurred at BLOX. Report the requestId to BLOX |
Error examples
401 UNAUTHORIZED
{
"code": "UNAUTHORIZED",
"message": "API key is required",
"requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}Shared response schemas
Beneficiary object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Send this value as beneficiaryId when you create a payout |
name | string | Beneficiary name in uppercase |
accountNumber | string | null | Bank account number. null for a DuitNow proxy beneficiary |
bank.code | string | Short bank code |
bank.name | string | Name of the bank |
bank | object | null | Set for a bank beneficiary. null for a proxy beneficiary, because PayNet supplies the bank at payment time |
proxy.type | string | NRIC, PSPT, BREG, MBNO, or ARMN |
proxy.value | string | The identifier as the beneficiary registered it with PayNet |
proxy.countryCode | string | null | ALPHA-3 code of the country of issue. Set for PSPT. null for other types |
proxy | object | null | null for a bank beneficiary |
autoWithdrawalAddress | string | null | The trigger address of the beneficiary, or null if it does not have one |
bankAccountVerified | boolean | true after the holder name matched at loose or strict, with or without nameCheck. You can also pay a beneficiary with the value false |
bankAccountVerifiedAt | string (ISO 8601) | null | The time when the bank confirmed the account. null when bankAccountVerified is false |
bankAccountNameMatch | string | null | "strict" or "loose": the level at which the name matched on the last check. null if BLOX did not compare the name |
createdAt | string (ISO 8601) | Creation time |
updatedAt | string (ISO 8601) | Last update time |
Meaning of bankAccountVerified and bankAccountNameMatch together:
bankAccountVerified | bankAccountNameMatch | Meaning |
|---|---|---|
true | "strict" or "loose" | The name matched at that level on the last check |
true | null | BLOX verified the beneficiary |
false | null | Not verified |
After bankAccountVerified changes to true, it stays true. If a later Verify beneficiary request returns 400, the value does not change.
Each beneficiary has bank and accountNumber set, or proxy set. The fields that are not set are null. The response always includes all of these keys. To find the type of beneficiary, check if proxy is null.
Payout object
| Field | Type | Description |
|---|---|---|
payoutId | string (UUID) | Unique payout identifier |
status | string | INITIATED, SETTLED, REVERSED, or RETURNED |
type | string | STANDARD for a payout that you create. PREFUND_RETURN when BLOX returns part of your prefund balance to you |
amount | string | Requested amount in sen |
fee | string | Fee in sen |
netAmount | string | Amount sent to the beneficiary |
beneficiaryId | string (UUID) | null | The beneficiary of the payout. null for a PREFUND_RETURN |
bankAccountId | string (UUID) | null | Your bank account, for a PREFUND_RETURN. null for other types |
idempotencyKey | string | null | The idempotency key of the create request. null for a PREFUND_RETURN |
reference | string | The reference that the bank gets with the payout |
statusReason | string | null | A stable reason key from the table below |
createdAt | string (ISO 8601) | Creation time |
submittedAt | string (ISO 8601) | null | The time when BLOX sent the payout to the bank |
Each payout has beneficiaryId or bankAccountId set, but not the two. All payouts that you create are STANDARD and have a beneficiary.
The create response always has submittedAt: null. To get the submission time, get the payout later. An INITIATED payout with submittedAt: null is not at the bank yet. An INITIATED payout with a submittedAt value is at the bank and has no final result yet.
Status reason values
| Value | Meaning |
|---|---|
pending | BLOX sent the payout. The bank did not send a final response yet |
rejected_by_bank | The bank rejected the payout |
returned_by_bank | The bank returned the payout after settlement |
invalid_beneficiary_bank | BLOX cannot send payouts to this bank |
under_review | BLOX holds the payout for manual review. Contact BLOX |
provider_unavailable | Temporary fault at the payment provider |
reversed_by_support | BLOX reversed the payout manually because it did not complete |
proxy_destination_not_supported | The beneficiary is a DuitNow proxy. Your account does not support payouts to a proxy |
provider_error | The payment network reported a different error. Contact BLOX and give the payoutId |
Use these keys in your logic. Do not use human-readable messages. The REST response and Webhook Events use the same values.