Skip to content
LogoLogo

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.

MethodEndpointPurpose
GET/v1/payout/prefund/balanceGet the available and pending balances
GET/v1/payouts/active-banksGet the bank code for a beneficiary
POST/v1/payouts/beneficiariesRegister a reusable beneficiary
GET/v1/payouts/beneficiariesList active beneficiaries
GET/v1/payouts/beneficiaries/{beneficiaryId}Get one beneficiary
DELETE/v1/payouts/beneficiaries/{beneficiaryId}Deactivate a beneficiary
POST/v1/payoutsCreate 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

GET/v1/payout/prefund/balanceAPI key

Returns 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"
}
FieldTypeDescription
availablestringAmount in sen that you can use for payouts
inFlightstringThe netAmount of accepted payouts that do not have a final status
receivablestringUsually "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
StatuscodeWhen
401UNAUTHORIZEDThe API key is missing, unknown, or inactive
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
429RATE_LIMITEDThe endpoint or account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

GET/v1/payouts/active-banksAPI key

Returns 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"
  }
]
FieldTypeDescription
namestringThe name of the bank or e-wallet
bankCodestringThe BIC of the bank

Use this bankCode value in all request fields with the name bankCode.

Error responses
StatuscodeWhen
401UNAUTHORIZEDThe API key is missing, unknown, or inactive
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
429RATE_LIMITEDThe endpoint or account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

POST/v1/payouts/beneficiariesAPI key + signature

Registers 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:

FieldTypeRequiredConstraints
namestringYes2–96 characters
bankCodestringYes1–11 characters. Use a code from /v1/payouts/active-banks
accountNumberstringYes3–20 digits
nameCheckstringNostrict 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.

FieldTypeRequiredConstraints
namestringYes2–96 characters
proxyTypestringYesNRIC, PSPT, BREG, MBNO, or ARMN
proxyValuestringYes3–40 characters, in the format for its proxyType. Refer to the table below
countryCodestringOnly for PSPTALPHA-3 code of the country of issue, 3 letters, for example MYS
nameCheckstringNostrict or loose. Refer to Name check below
proxyTypeIdentifierproxyValue format
MBNOMobile numberDigits only, with the country dialing code and without a +. 7–19 digits. Example: 60123456789
NRICMalaysian identity card numberLetters and digits only. Example: 900101015432
PSPTPassport number. Also send countryCodeLetters and digits only. Example: E39412345
BREGBusiness registration numberLetters and digits only. Example: 202201001234
ARMNArmy numberLetters 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:

nameCheckPasses whenOtherwise
omittedThe name result does not stop the registration—
strictThe 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 beneficiary400 BENEFICIARY_NAME_MISMATCH. If the name passes at loose, the message tells you
looseAll 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 IC400 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 resultnameCheck omittedloosestrict
Account invalid, dormant, closed, or blocked400 INVALID_REQUESTsamesame
BLOX cannot connect to the bank503 SERVICE_UNAVAILABLE. Retry with the same Idempotency-Keysamesame
The bank does not return a holder nameRegistered, bankAccountVerified: false400 NAME_CHECK_UNAVAILABLEsame
Name matches at strictRegistered, bankAccountNameMatch: "strict"samesame
Name matches at loose onlyRegistered, bankAccountNameMatch: "loose"same400 BENEFICIARY_NAME_MISMATCH. The message tells you that the name passes at loose
Name does not matchRegistered, bankAccountVerified: false400 BENEFICIARY_NAME_MISMATCHsame

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: bankAccountVerified does 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
StatuscodeWhen
400VALIDATION_FAILEDA body field is missing, malformed, or outside its constraints
400INVALID_REQUESTThe Idempotency-Key is missing or malformed. Also returned when the bank rejects the account
400UNKNOWN_BENEFICIARYThe bankCode is not in your /v1/payouts/active-banks list. Also returned for a DuitNow proxy if your account does not support proxies
400BENEFICIARY_NAME_MISMATCHOnly with nameCheck. The name does not match the bank records at the requested level
400NAME_CHECK_UNAVAILABLEOnly with nameCheck. The bank cannot give a name result for this account. Retry without nameCheck
401UNAUTHORIZEDThe API key or signature is missing or invalid
403LIMIT_EXCEEDEDYou have the maximum number of active beneficiaries
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
422CONFLICTYou used the idempotency key before with a different body
429RATE_LIMITEDThe endpoint or account rate limit is exceeded
503SERVICE_UNAVAILABLEThe bank could not confirm the beneficiary at this time. BLOX did not register the beneficiary. Retry with the same Idempotency-Key
5xxINTERNAL_ERRORAn 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

GET/v1/payouts/beneficiariesAPI key

Returns the active beneficiaries. The newest beneficiary is first.

QueryTypeDescription
limitinteger1–100. The default is 25
cursorstringThe nextCursor value from the previous response. Treat it as an opaque string
searchstringFinds 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
StatuscodeWhen
400VALIDATION_FAILEDA query parameter is malformed or outside its constraints
401UNAUTHORIZEDThe API key is missing, unknown, or inactive
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
429RATE_LIMITEDThe account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

GET/v1/payouts/beneficiaries/{beneficiaryId}API key

Returns one active beneficiary. If the ID is unknown, inactive, or belongs to a different account, the response is 404 NOT_FOUND.

Path parameterTypeDescription
beneficiaryIdstring (UUID)The ID of a beneficiary of your account

Response — 200: Beneficiary object.

Error responses
StatuscodeWhen
401UNAUTHORIZEDThe API key is missing, unknown, or inactive
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
404NOT_FOUNDThe beneficiary is unknown, inactive, or belongs to a different account
429RATE_LIMITEDThe account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

DELETE/v1/payouts/beneficiaries/{beneficiaryId}API key + signature

Deactivates 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
StatuscodeWhen
401UNAUTHORIZEDThe API key or signature is missing or invalid
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
404NOT_FOUNDThe beneficiary is unknown, inactive, or belongs to a different account
429RATE_LIMITEDThe account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

POST/v1/payouts/beneficiaries/{id}/verifyAPI key + signature

Checks the holder name of a registered beneficiary again, at the level that you select. The limit is 20 requests per minute for each account.

FieldTypeRequiredConstraints
nameCheckstringYesstrict 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
StatuscodeWhen
400VALIDATION_FAILEDnameCheck is missing, or it is not strict or loose
400INVALID_REQUESTThe bank rejected the account
400BENEFICIARY_NAME_MISMATCHThe name does not match the bank records at the requested level. The verified status does not change
400NAME_CHECK_UNAVAILABLEThe bank cannot give a name result for this account
401UNAUTHORIZEDThe API key or signature is missing or invalid
403FEATURE_DISABLEDPayouts are not enabled for the account
403ACCOUNT_TERMINATEDThe account is terminated
404NOT_FOUNDThe beneficiary is unknown, inactive, or belongs to a different account
429RATE_LIMITEDYou sent more than 20 verification requests in one minute
503SERVICE_UNAVAILABLEBLOX could not connect to the bank. The beneficiary did not change. Retry later
5xxINTERNAL_ERRORAn 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

POST/v1/payoutsAPI key + signature

Creates 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

HeaderRequiredDescription
Idempotency-KeyYesA UUID v4. Store one key for each payout before you send it. Use the same key when you retry

Body

FieldTypeRequiredConstraints
amountintegerYes100–10000000 sen
feeModestringYesDEDUCT_FROM_AMOUNT or CHARGE_TO_PREFUND
beneficiaryIdstring (UUID)ConditionalSend beneficiaryId or beneficiary, but not the two. A payout does not check a registered beneficiary again. To check it, use Verify beneficiary
beneficiaryobjectConditionalThe same fields as Create beneficiary
referencestringNo1–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, or 503, 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
StatuscodeWhen
400VALIDATION_FAILEDThe body is invalid. This includes a body with the two beneficiary fields or with no beneficiary field
400INVALID_REQUESTThe 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
400INSUFFICIENT_BALANCEThe prefund balance is less than netAmount + fee
400UNKNOWN_BENEFICIARYThe 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
400BENEFICIARY_NAME_MISMATCHOnly with nameCheck on an inline beneficiary. The name does not match the bank records at the requested level
400NAME_CHECK_UNAVAILABLEOnly with nameCheck on an inline beneficiary. The bank cannot give a name result. Retry without nameCheck
400LIMIT_EXCEEDEDThe configured daily or monthly payout limit is reached
401UNAUTHORIZEDThe API key or signature is missing or invalid
403LIMIT_EXCEEDEDA new inline beneficiary would make the number of active beneficiaries more than the limit
403FEATURE_DISABLEDPayouts are disabled. Or, payouts are suspended because of a prefund reversal that the balance did not cover
403ACCOUNT_FROZEN / ACCOUNT_PENDING_VERIFICATION / ACCOUNT_TERMINATEDThe account status does not allow payouts
422CONFLICTYou used the idempotency key before with a different body
429RATE_LIMITEDThe endpoint or account rate limit is exceeded
503SERVICE_UNAVAILABLEBLOX 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
5xxINTERNAL_ERRORAn 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

GET/v1/payouts/{id}API key

Returns 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 parameterTypeDescription
idstring (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
StatuscodeWhen
401UNAUTHORIZEDThe API key is missing, unknown, or inactive
403FEATURE_DISABLEDPayouts are disabled. This includes a suspension because of a prefund reversal that the balance did not cover
403ACCOUNT_TERMINATEDThe account is terminated
404NOT_FOUNDThe payout is unknown or belongs to a different account
429RATE_LIMITEDThe endpoint or account rate limit is exceeded
5xxINTERNAL_ERRORAn 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

FieldTypeDescription
idstring (UUID)Send this value as beneficiaryId when you create a payout
namestringBeneficiary name in uppercase
accountNumberstring | nullBank account number. null for a DuitNow proxy beneficiary
bank.codestringShort bank code
bank.namestringName of the bank
bankobject | nullSet for a bank beneficiary. null for a proxy beneficiary, because PayNet supplies the bank at payment time
proxy.typestringNRIC, PSPT, BREG, MBNO, or ARMN
proxy.valuestringThe identifier as the beneficiary registered it with PayNet
proxy.countryCodestring | nullALPHA-3 code of the country of issue. Set for PSPT. null for other types
proxyobject | nullnull for a bank beneficiary
autoWithdrawalAddressstring | nullThe trigger address of the beneficiary, or null if it does not have one
bankAccountVerifiedbooleantrue after the holder name matched at loose or strict, with or without nameCheck. You can also pay a beneficiary with the value false
bankAccountVerifiedAtstring (ISO 8601) | nullThe time when the bank confirmed the account. null when bankAccountVerified is false
bankAccountNameMatchstring | null"strict" or "loose": the level at which the name matched on the last check. null if BLOX did not compare the name
createdAtstring (ISO 8601)Creation time
updatedAtstring (ISO 8601)Last update time

Meaning of bankAccountVerified and bankAccountNameMatch together:

bankAccountVerifiedbankAccountNameMatchMeaning
true"strict" or "loose"The name matched at that level on the last check
truenullBLOX verified the beneficiary
falsenullNot 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

FieldTypeDescription
payoutIdstring (UUID)Unique payout identifier
statusstringINITIATED, SETTLED, REVERSED, or RETURNED
typestringSTANDARD for a payout that you create. PREFUND_RETURN when BLOX returns part of your prefund balance to you
amountstringRequested amount in sen
feestringFee in sen
netAmountstringAmount sent to the beneficiary
beneficiaryIdstring (UUID) | nullThe beneficiary of the payout. null for a PREFUND_RETURN
bankAccountIdstring (UUID) | nullYour bank account, for a PREFUND_RETURN. null for other types
idempotencyKeystring | nullThe idempotency key of the create request. null for a PREFUND_RETURN
referencestringThe reference that the bank gets with the payout
statusReasonstring | nullA stable reason key from the table below
createdAtstring (ISO 8601)Creation time
submittedAtstring (ISO 8601) | nullThe 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

ValueMeaning
pendingBLOX sent the payout. The bank did not send a final response yet
rejected_by_bankThe bank rejected the payout
returned_by_bankThe bank returned the payout after settlement
invalid_beneficiary_bankBLOX cannot send payouts to this bank
under_reviewBLOX holds the payout for manual review. Contact BLOX
provider_unavailableTemporary fault at the payment provider
reversed_by_supportBLOX reversed the payout manually because it did not complete
proxy_destination_not_supportedThe beneficiary is a DuitNow proxy. Your account does not support payouts to a proxy
provider_errorThe 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.