Cashout
Cashout pays fiat directly to a recipient's bank account, debiting your crypto fund. There is no QR code: you name the recipient yourself.
- Quote it —
review-cashout. - Create it —
create-cashout. - Track it —
orders/details, plus a webhook.
The user must be KYC-verified first. See KYC. Cashout and scan-to-pay share the same order lookup, the same order object, and the same webhook.
Older documentation gives the signed string for create-cashout as
partnerCode|externalOrderId|currency|fiatAmount|bankCode|bankAccountNumber|content|userEmail|secretKey.
That is scan-to-pay's string, pasted into the cashout page. It has never been what the server checks. The real one is below: different fields, different order, and the bank details are not signed at all.
If your cashout integration fails with INVALID_SIGNATURE and you cannot see why, this is why.
Quote a cashout
POST /api/v2/orders/review-cashout
Signed string
partnerCode|userEmail|fiatCurrency|fiatAmount|cryptoCurrency|<secretKey>
Request — every field is required.
| Field | Type | Notes |
|---|---|---|
partnerCode | string | |
userEmail | string | The end user. |
fiatCurrency | string | VND, PHP, IDR… |
fiatAmount | number | What the recipient receives. |
cryptoCurrency | string | The crypto to debit — USDT. |
signature | string |
Response data
| Field | Type | Notes |
|---|---|---|
fiatAmount | number | Echoed from your request. |
currency | string | The crypto code. Some schemas type this as a number; it is a string. |
price | number | |
tokenAmount | number | The crypto that will be debited. |
totalPayment | number | |
processingFee, systemFee | number | |
signature | string | Over fiatAmount|currency|price|tokenAmount|totalPayment|secretKey. |
The quote does not return the minimum and maximum amounts, though v3's does. Handle 24 and 25
instead, whose messages carry the live limit.
Errors — 46 SERVICE_NOT_AVAILABLE, 24 / 25 (below min / above max), 16
AMOUNT_OUT_OF_RANGE (below the payment provider's own floor, 10,000 VND for Vietnam), 7
PARTNER_INSUFFICIENT_BALANCE, 9 SERVICE_UNDER_MAINTENANCE, plus the KYC codes (34, 59, 60,
61, 62, 64) and the shared ones.
Create a cashout
POST /api/v2/orders/create-cashout
Signed string
partnerCode|externalOrderId|userEmail|cryptoCurrency|fiatCurrency|fiatAmount|<secretKey>
It signs cryptoCurrency (not currency) and fiatCurrency, and it does not sign the recipient's
bank details: those live in extendInfo, outside the signature.
Every segment is always interpolated, so omitting fiatCurrency or fiatAmount — neither of which is
rejected as missing — puts the literal text undefined into the string. The signature still verifies,
because you and the server build it the same way, and the order then fails with 46. Send both.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
partnerCode | string | Yes | |
externalOrderId | string | Yes | Your unique id, and the idempotency key. |
userEmail | string | Yes | Must be KYC-verified. |
cryptoCurrency | string | Yes | The crypto to debit — USDT. |
fiatCurrency | string | Yes | Omitting it is not rejected as a missing field, and no default is applied. See above. |
fiatAmount | number | Yes | Same. |
extendInfo | object | Yes | The recipient. |
webhookSecretKey | string | No | Omit it and no callback is ever sent. |
signature | string | Yes |
extendInfo — the recipient
| Field | Type | Required | Notes |
|---|---|---|---|
recipientName | string | Yes | |
recipientNumber | string | Yes | The bank account number. |
recipientBankCode | string | Yes | From the bank list. |
transferContent | string | No | The transfer remark. Unaccented letters only, where the provider checks it. |
recipientType | string | No | If you send it, it must be a recognised value — otherwise 5. |
{
"partnerCode": "ACME",
"externalOrderId": "acme-co-1042",
"cryptoCurrency": "USDT",
"fiatCurrency": "VND",
"fiatAmount": 5000000,
"webhookSecretKey": "81663eae...",
"extendInfo": {
"recipientName": "NGUYEN VAN A",
"recipientNumber": "0123456789",
"recipientBankCode": "VCB",
"transferContent": "ALIX CASHOUT 1042"
},
"signature": "I7XDfTngWIraq...=="
}
Response data — the order object, with type: "CASHOUT".
For a cashout, tokenTransfer.address, tokenTransfer.network and bankTransfer.qrUrl are null —
there is no on-chain leg to the recipient and no QR to show.
Errors — everything review-cashout can return, plus:
11 DUPLICATE_TRANSACTION_ID | externalOrderId is already used. |
13 WEBHOOK_SECRET_NOT_FOUND | webhookSecretKey matches no registered endpoint. Checked before anything else, so the order is not created. |
18 INVALID_BANK_ACCOUNT | extendInfo is missing, or the account could not be verified. |
56 BANK_NOT_SUPPORTED | recipientBankCode is not in the provider's list. |
17 INVALID_TRANSFER_CONTENT | transferContent has accents or special characters. |
27 USER_EMAIL_INVALID | userEmail is malformed. |
5 INVALID_REQUEST_DATA | recipientType is not a recognised value. |
12 TRANSACTION_CREATE_FAILED | The payout failed after the order was created. You are already refunded, and data holds the order. |