Crypto Pay-Out
Overview
The Crypto Pay-Out API transfers digital currency from the merchant balance to a specified on-chain address. Once accepted, the order enters the "order received (processing)" state; the platform notifies the merchant of the final result via callback after settlement.
Rely on the asynchronous result
A successful order creation only means the request was accepted. The response always returns status = 1 (order received, processing) and the platform then initiates the on-chain transfer, so the order response cannot be used to determine the payout result. Rely on the callback or the inquiry endpoint.
Common request headers (including the mandatory Nonce), common body fields and the unified response envelope are described in Crypto Integration Overview.
Request path
Domain: Prefer unified host
openapi.toppayment.com(path unchanged); legacy crypto domain (global-digit-openapi.toppayment.com) remains available.
| Environment | URL |
|---|---|
| Sandbox | https://openapi.toppayment.com/crypto/sandbox/disbursement/cash |
| Production | https://openapi.toppayment.com/crypto/disbursement/cash |
Request headers
| Field | Required | Type | Description |
|---|---|---|---|
| Content-Type | M | String | Fixed value: application/json |
| Nonce | M | String(16-64) | Anti-replay random string, unique within 10 minutes, excluded from the signature |
IP whitelist
Pay-out endpoints are protected by an egress IP whitelist. Submit your egress IP to support in advance, otherwise 1001 (IP whitelist exception) or 1004 (IP whitelist not configured) is returned.
Request body
| Field | Required | Type | Description |
|---|---|---|---|
| mchNo | M | String(32) | Merchant number In sandbox use the SD-prefixed sandbox merchant number |
| orderNum | M | String(64) | Merchant order number Must be unique per merchant; duplicates return 0001 |
| money | M | String | Payout quantity Numeric string with at most 6 decimal places Example: 10.5 |
| currency | M | String(16) | Coin Charset A-Z a-z 0-9 _, example: USDT |
| netWork | M | String(100) | Chain network Example: TRC20Must match the chain that inAddress belongs to |
| inAddress | M | String(255) | Beneficiary address Validate the format and chain before submitting |
| feeType | M | String | Fee bearer0: fee deducted from the payout quantity, beneficiary receives money - fee1: fee charged separately, the account is debited money + fee and the beneficiary receives money |
| downNotifyUrl | M | String(255) | Asynchronous notification URL Must be a publicly reachable HTTP/HTTPS URL, see Callback |
| timestamp | M | String(13) | Request timestamp (13-digit milliseconds), within ±5 minutes |
| sign | M | String | Signature, see Signature |
| name | O | String(64) | Beneficiary name |
| O | String(64) | Beneficiary email | |
| phone | O | String(32) | Beneficiary phone |
| tenantCode | O | String(32) | Sub-tenant code, defaults to the default tenant |
| countryCode | O | String(16) | Country code, not needed for crypto |
feeType explained
Using money = 100 and fee = 1:
| feeType | Account debited | Beneficiary receives | Use case |
|---|---|---|---|
0 | 100 | 99 | Fee borne by the beneficiary |
1 | 101 | 100 | Fee borne by the merchant, so the beneficiary receives a round amount |
fee is derived from the merchant fee template (fixed fee + rate × quantity). The authoritative value is the fee returned in the order response and callback. If no fee template or channel matches, 0003 is returned.
Request example
Content-Type: application/json
Nonce: c41f7ae9028b56d3f1a4
{
"mchNo": "{{mchNo}}",
"orderNum": "CW1788506801001",
"money": "10.5",
"currency": "USDT",
"netWork": "TRC20",
"inAddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"feeType": "0",
"downNotifyUrl": "https://example.com/notify/loan",
"name": "Jack Ma",
"email": "[email protected]",
"phone": "081234567890",
"timestamp": "1749451858772",
"sign": "replace with generated signature"
}
Response body
| Field | Required | Type | Description |
|---|---|---|---|
| success | M | Boolean | Whether the request succeeded |
| code | M | String | Response code 9999: success; otherwise see Exception Codes |
| msg | O | String | Response message, null on success |
| timeStamp | M | Number | Server response time (milliseconds) |
| data | M | Object | Response payload |
| orderNum | M | String | Merchant order number |
| platOrderNum | M | String | Platform order number The key used for later inquiries and callbacks |
| amount | M | Number | Payout quantity, equal to the request money |
| fee | M | Number | Fee |
| feeType | M | Number | Fee bearer0: deducted from the payout quantity; 1: charged separately |
| status | M | Number | Order status code Always 1 (order received, processing) on successful creation; see the table below |
| statusMsg | M | String | Status description Order creation and inquiry return the unified display status (e.g. PENDING)Callbacks return the detailed status (e.g. ORDER_RECEIVED_PROCESSING); the two value sets differ |
| currency | M | String | Coin |
| netWork | M | String | Chain network |
Pay-out status codes
| status | Detailed status (callback statusMsg) | Unified display status (order/inquiry statusMsg) | Description | Final |
|---|---|---|---|---|
0 | INITIAL_STATE_PENDING_ORDERS | PENDING | Initial state (awaiting acceptance) | No |
1 | ORDER_RECEIVED_PROCESSING | PENDING | Order received (processing) | No |
2 | PROCESSED_SUCCESSFULLY | SUCCESS | Payout succeeded | Yes |
3 | CANCELED_SUCCESSFULLY | CANCELLED | Cancelled (funds unfrozen and returned) | Yes |
4 | PROCESSING_FAILED | FAILED | Payout failed (funds unfrozen and returned) | Yes |
5 | LOANING | PROCESSING | On-chain transfer in progress | No |
99 | PENDING_ORDER | PENDING | Awaiting acceptance | No |
Two sets of statusMsg values
The same status yields different statusMsg wording depending on the endpoint: order creation and inquiry return the unified display status (PENDING / PROCESSING / SUCCESS / FAILED / CANCELLED), while callbacks return the detailed status (ORDER_RECEIVED_PROCESSING, PROCESSED_SUCCESSFULLY, etc.). Base your business logic on the numeric status and treat statusMsg as display/logging only.
Response example
{
"success": true,
"code": "9999",
"msg": null,
"timeStamp": 1767840272829,
"data": {
"orderNum": "CW1788506801001",
"platOrderNum": "CW20260825143024553",
"amount": 10.500000,
"fee": 0.700000,
"feeType": 0,
"status": 1,
"statusMsg": "PENDING",
"currency": "USDT",
"netWork": "TRC20"
}
}
{
"success": false,
"code": "1002",
"msg": "[1002]Insufficient available balance!",
"timeStamp": 1767840272829,
"data": null
}
Notes
Address and network must match
inAddress and netWork must belong to the same chain. A cross-chain address makes the assets unrecoverable and the platform cannot retrieve funds sent to a wrong address. Validate the address format before submitting.
- A successful order immediately freezes the corresponding balance (including the fee); the freeze is released automatically on cancellation or failure.
- Insufficient balance returns
1002. In sandbox,money = 20000000reliably triggers this error for testing. - Reusing the same
mchNo+orderNumreturns0001(The order already exists!); concurrent duplicate submissions return0004. - The order response only means the request was accepted. Rely on the Callback or Order Inquiry for the final result.
Next steps
- Order status and transaction hash: Crypto Order Inquiry
- Payout result notification: Crypto Callback
