Callbacks
When a pay-in is received, or a payout finishes (success or refused), the platform sends a POST request to your callback URL.
Callback URL
| Order | URL used |
|---|---|
| Pay-in | callbackUrl passed when creating the order; otherwise the pay-in callback URL in the merchant console |
| Payout | callbackUrl passed when creating the order; otherwise the payout callback URL in the merchant console |
The URL must be a publicly reachable https address. Only payouts created through the OpenAPI get callbacks; payouts submitted manually in the merchant console don't.
Request
POST /your/callback/path HTTP/1.1
Content-Type: application/json
{"encryptedData":"Base64 string"}encryptedData is JSON encrypted with your public key (PKCS#1 v1.5). Decrypt it with your private key.
Pay-in callback
{
"OrderNo": "PI1845290017342758912",
"MerchantNo": "your merchant number",
"MerchantOrderNo": "A20261013001",
"PayTime": "2026-10-13T12:00:00",
"OrderState": 6,
"Quantity": 13.7,
"FiatCurrency": 100.0
}| Field | Type | Description |
|---|---|---|
OrderNo | string | Platform order number |
MerchantNo | string | Merchant number |
MerchantOrderNo | string | Your merchant order number |
PayTime | string | When the customer paid, UTC+8, yyyy-MM-ddTHH:mm:ss |
OrderState | int | Always 6 (Released: the platform has confirmed the money and credited the USDT to your balance) |
Quantity | number | USDT amount of the order (before fees) |
FiatCurrency | number | Fiat amount (the order amount) |
Payout callback
{
"OrderNo": "PO1845290017342758913",
"MerchantNo": "your merchant number",
"MerchantOrderNo": "PO20261013001",
"PayTime": "2026-10-13T12:30:00",
"state": 4
}| Field | Type | Description |
|---|---|---|
OrderNo | string | Platform payout number |
MerchantNo | string | Merchant number |
MerchantOrderNo | string | Your merchant order number |
PayTime | string | null | When it finished, UTC+8 |
state | int | 4 succeeded; 5 refused (the frozen USDT has been refunded) |
Field name casing
Callback fields start with a capital letter (OrderNo), while the payout state is lowercase state — unlike the camelCase API responses. Parse field names case-insensitively. Numbers in callbacks are not rounded; Get order is the source of truth.
What to reply
After processing, reply with HTTP 2xx and a body of either:
- plain text
success(orok, case-insensitive, no surrounding spaces or newlines), or - JSON
{"code":200}
Anything else (timeouts, non-2xx, other content) counts as a failure and the platform will retry.
Retries
The first callback is sent within 30 seconds after the order completes. Failed callbacks are retried at these intervals, at most 5 attempts in total:
| Attempt | After the previous one |
|---|---|
| 1 | within 30 seconds of completion |
| 2 | 3 minutes |
| 3 | 9 minutes |
| 4 | 27 minutes |
| 5 | 81 minutes |
After 5 failures the platform stops. You can still see the order in the merchant console or with Get order. A failed callback does not mean the payment failed.
Required checks
Callbacks are not signed
Callbacks are only encrypted with your public key — they are not signed. Anyone who has your public key can forge a "paid" callback, so never credit a customer based on the callback content alone.
Handle each callback in this order (the sample code does all of this):
- Decrypt it; if that fails, reply with a failure.
- Check the merchant number is yours.
- Deduplicate: process the same
OrderNo+ state only once. The platform retries, and your own processing may be interrupted before you replysuccess. Put a unique index on "platform order number + state" and record it in the same database transaction as the crediting. - Verify with Get order using
MerchantOrderNo, and trust its result:- pay-in: the state must be
6(Released) or3(Success), and the amount must equal the callback'sFiatCurrencyand the amount of the order in your own system; - payout: the state must equal the callback's
state, and the amount must equal the payout amount in your own system.
- pay-in: the state must be
- Only then credit / update your order, and finally reply
success.
Also limit the request body size (a few KB is enough), accept POST only, and don't log full request bodies.