Epusdt API Documentation
Developers can integrate Epusdt payment collection into business systems through its HTTP API. This document follows the routes available in the current source code.
The legacy
POST /api/v1/order/create-transactionroute is no longer registered. UsePOST /payments/gmpay/v1/order/create-transactionto create orders.
API Overview
| Scenario | Method | Path | Signature Required |
|---|---|---|---|
| Create GMPay transaction | POST | /payments/gmpay/v1/order/create-transaction | Yes |
| Get public payment config | GET | /payments/gmpay/v1/config | No |
| Cashier page | GET | /pay/checkout-counter/{trade_id} | No |
| Cashier initialization data | GET | /pay/checkout-counter-resp/{trade_id} | No |
| Check payment status | GET | /pay/check-status/{trade_id} | No |
| Switch payment network/channel | POST | /pay/switch-network | No |
| Create EPay-compatible transaction | GET/POST | /payments/epay/v1/order/create-transaction/submit.php | Yes |
| OkPay platform callback | POST | /payments/okpay/v1/notify | OkPay signature |
Unified Response Format
Except for redirects and plain-text callback endpoints, APIs return JSON:
{
"status_code": 200,
"message": "success",
"data": {},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}Fields:
| Field | Type | Description |
|---|---|---|
status_code | integer | Business status code. Success is 200; error codes are listed at the end of this document. |
message | string | Response message. |
data | object/null | Response data. |
request_id | string | Request ID generated by the server. |
Signature errors return HTTP 401. Business errors usually return HTTP 400 with a specific business code in status_code.
Signature Rules
The current version uses unified merchant credentials. Each request must include pid; the server uses pid to find the matching secret_key as the signing key. A default installation creates a default API key with PID 1000.
GMPay Signature
Since
v2.0.0, GMPay uses HMAC-SHA256. This is a breaking change from the pre-v2 MD5 rule. Existing GMPay clients must update request and callback signing before deployingv2.0.0or later. The EPay-compatible API still uses MD5.
- Sort all non-empty parameters by ASCII key order in ascending order.
- Join them as
key=valuepairs with&. - Exclude the
signaturefield. - Calculate HMAC-SHA256 using the merchant
secret_keyas the HMAC key and the joined parameter string as the message. - Encode the result as 64-character lowercase hexadecimal text and send it as
signature.
Notes:
pidmust participate in the signature.- GMPay
payment_typeis optional. If a non-emptypayment_typeis submitted, it must participate in the GMPaysignaturecalculation like other non-empty parameters. - Empty strings and
nulldo not participate in signing. - Parameter names are case-sensitive.
- JSON numbers participate in signing using the server-side numeric format. For example,
100.00is parsed as100; useapplication/x-www-form-urlencodedif you need to preserve string formatting.
Example parameters:
pid=1000
order_id=ORD202605230001
currency=cny
token=usdt
network=tron
amount=100
notify_url=https://merchant.example/notify
redirect_url=https://merchant.example/return
name=VIPThe following example assumes secret_key is epusdt_secret_key, only to demonstrate signature calculation.
Canonical parameter string:
amount=100¤cy=cny&name=VIP&network=tron¬ify_url=https://merchant.example/notify&order_id=ORD202605230001&pid=1000&redirect_url=https://merchant.example/return&token=usdtResult:
signature=6f874b1919d95081835e2809b620e354a5866f5a6dbb2e432d1627f1eb10059dPHP Signature Examples
GMPay uses the signature field and excludes only signature during signing:
function gmpaySign(array $params, string $secretKey): string
{
unset($params['signature']);
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$pairs[] = $key . '=' . $value;
}
return hash_hmac('sha256', implode('&', $pairs), $secretKey);
}The EPay-compatible API uses the sign field and excludes sign plus sign_type during signing:
function epaySign(array $params, string $secretKey): string
{
unset($params['sign'], $params['sign_type']);
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$pairs[] = $key . '=' . $value;
}
return strtolower(md5(implode('&', $pairs) . $secretKey));
}Create a GMPay Transaction
POST /payments/gmpay/v1/order/create-transaction
Supported content types:
Content-Type: application/jsonContent-Type: application/x-www-form-urlencoded
GMPay Request Example
{
"pid": "1000",
"order_id": "ORD202605230001",
"currency": "cny",
"token": "usdt",
"network": "tron",
"amount": 100,
"notify_url": "https://merchant.example/notify",
"redirect_url": "https://merchant.example/return",
"name": "VIP",
"signature": "6f874b1919d95081835e2809b620e354a5866f5a6dbb2e432d1627f1eb10059d"
}GMPay Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
pid | string | Yes | Merchant PID, used to find the API key and included in signing. |
order_id | string | Yes | Merchant order number, maximum 32 characters, must be unique. |
currency | string | Yes | Fiat currency, such as cny or usd. |
token | string | Conditionally required | Payment token, such as usdt, trx, usdc, or sol. GMPay can omit both token and network to create a placeholder order with status 4. |
network | string | Conditionally required | Payment network, such as tron, solana, ethereum, bsc, polygon, or plasma. GMPay can omit both network and token to create a placeholder order with status 4. |
amount | number | Yes | Fiat amount; must be greater than 0.01. |
notify_url | string | Yes | Asynchronous callback URL for successful payment. |
redirect_url | string | No | Synchronous redirect URL after payment completion. |
name | string | No | Product or order name. |
payment_type | string | No | GMPay-compatible field. It is not required. If a non-empty value is sent, it must participate in the GMPay signature calculation. If omitted in normal GMPay, the backend stores it as Gmpay; sending Epay case-insensitively stores it as Epay and uses the EPay callback format, and PID must be numeric. |
signature | string | Yes | GMPay signature. |
token and network must either both be provided or both omitted. If both are omitted, only a placeholder order containing amount/currency is created with status 4; no wallet is assigned, no on-chain payable amount is calculated, and no transaction amount is locked. The cashier can later call /pay/switch-network to choose a specific chain/token or OkPay. Providing only one of the two fields returns a parameter error.
Call /payments/gmpay/v1/config first to obtain the available network and token combinations.
GMPay Success Response
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260523171652123456001",
"order_id": "ORD202605230001",
"amount": 100,
"currency": "CNY",
"actual_amount": 14.29,
"receive_address": "TTestTronAddress001",
"token": "USDT",
"status": 1,
"expiration_time": 1779530812,
"payment_url": "https://pay.example.com/pay/checkout-counter/20260523171652123456001"
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}| Field | Type | Description |
|---|---|---|
trade_id | string | Epusdt transaction number. |
order_id | string | Merchant order number. |
amount | number | Fiat amount submitted by the merchant. |
currency | string | Fiat currency. |
actual_amount | number | Crypto amount that must actually be paid. |
receive_address | string | Receiving address. |
token | string | Payment token. |
status | integer | Order status. Status 4 means waiting for the user to choose token/network. |
expiration_time | integer | Order expiration time as a Unix timestamp in seconds. |
payment_url | string | Cashier URL. This URL redirects to the frontend cashier. |
For a status 4 placeholder order, actual_amount is 0, while receive_address and token are empty. Expiration tasks or backend closure only change it to status 3; they do not unlock a transaction amount. On the first successful /pay/switch-network call, if a normal on-chain token/network is selected, the same parent order is completed in place with on-chain fields and becomes status 1, and only then is a real transaction lock created. If network=okpay is selected, the same parent order is converted in place into an OkPay order and returns the hosted OkPay payment link; no child order is created, and no local wallet address or on-chain lock is allocated. After the placeholder parent is first completed, is_selected remains false; a later selection of the same target marks the parent as selected, while switching to another payment target creates exactly one child order.
Get Public Payment Config
GET /payments/gmpay/v1/config
Returns cashier display settings, available chains/tokens, EPay defaults, and OkPay public configuration.
Public Config Success Response Example
{
"status_code": 200,
"message": "success",
"data": {
"supported_assets": [
{
"network": "tron",
"display_name": "TRON",
"tokens": ["TRX", "USDT"]
},
{
"network": "solana",
"display_name": "Solana",
"tokens": ["SOL", "USDC", "USDT"]
}
],
"site": {
"cashier_name": "Acme Cashier",
"logo_url": "https://cdn.example.com/logo.png",
"website_title": "Acme Payments",
"support_link": "https://example.com/support",
"background_color": "#0f172a",
"background_image_url": "https://cdn.example.com/background.png"
},
"epay": {
"default_token": "",
"default_currency": "cny",
"default_network": ""
},
"okpay": {
"enabled": false,
"allow_tokens": ["USDT", "TRX"]
},
"version": "v1.0.1"
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}supported_assets only includes combinations that satisfy all of the following:
- The chain is enabled.
- The chain has available wallet addresses.
- The chain has at least one enabled token.
Cashier Page
GET /pay/checkout-counter/{trade_id}
Used by the browser to open the cashier. The current implementation returns 301 and redirects to:
/cashier/{trade_id}The payment_url returned by the create transaction API is this address.
Cashier Initialization Data
GET /pay/checkout-counter-resp/{trade_id}
Used by the frontend cashier to read order display data. This endpoint only confirms that the order exists and returns basic data; call /pay/check-status/{trade_id} for the current payment status.
Cashier Init Success Response Example
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260523171652123456001",
"amount": 100,
"actual_amount": 14.29,
"token": "USDT",
"currency": "CNY",
"receive_address": "TTestTronAddress001",
"network": "tron",
"status": 1,
"payment_type": "gmpay",
"expiration_time": 1779530812000,
"redirect_url": "https://merchant.example/return",
"payment_url": "",
"created_at": 1779530212000,
"server_time": 1779530312000,
"is_selected": false
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}Note: expiration_time, created_at, and server_time returned by this endpoint are millisecond timestamps. server_time is the server timestamp used by the hosted cashier to calculate countdowns without relying on client clock accuracy.
If the order is a status 4 placeholder order, the response still uses the same parent trade_id, but on-chain payment fields have not been generated yet. This status can come from GMPay creation with empty token/network, or from EPay submit.php when neither the request nor database defaults provide a complete token/network:
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260523171652123456001",
"amount": 100,
"actual_amount": 0,
"token": "",
"currency": "CNY",
"receive_address": "",
"network": "",
"status": 4,
"payment_type": "gmpay",
"expiration_time": 1779530812000,
"redirect_url": "https://merchant.example/return",
"payment_url": "",
"created_at": 1779530212000,
"server_time": 1779530312000,
"is_selected": false
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}payment_type is the normalized integration type. The underlying order storage uses Epay/Gmpay; this endpoint returns lowercase epay/gmpay. epay uses the EPay callback format, while gmpay uses the default GMPay JSON callback format.
When the frontend sees status=4, it should show the network and token/payment-channel selector and call /pay/switch-network after the user chooses. After a successful on-chain selection, the parent order becomes status=1, and actual_amount, token, network, and receive_address are completed, but is_selected remains false until a later same-target selection marks it as selected. After a successful OkPay selection, the endpoint returns the same parent trade_id and a third-party payment_url; the parent order becomes status=1, is_selected=false, pay_provider=okpay, network=okpay, and receive_address=OKPAY.
Check Payment Status
GET /pay/check-status/{trade_id}
Payment Status Success Response Example
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260523171652123456001",
"status": 1
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}Order statuses:
| Value | Description |
|---|---|
1 | Waiting for payment |
2 | Payment successful |
3 | Expired |
4 | Waiting for payment network/token selection |
Submit Cashier Transaction Hash
POST /pay/submit-tx-hash/{trade_id}
This endpoint lets the hosted cashier submit an on-chain transaction hash for manual verification. It is intentionally narrower than the admin mark-paid action:
- the order must be an on-chain order
- the order must still be
status=1waiting for payment - expired orders are rejected before chain verification
- OkPay/provider orders are not supported
Admin mark-paid can repair waiting or expired on-chain orders after the submitted chain transaction is verified. The cashier endpoint stays waiting-order only.
Submit Transaction Hash Request
{
"block_transaction_id": "0xabc123..."
}Submit Transaction Hash Success Response
{
"status_code": 200,
"message": "success",
"data": {
"trade_id": "20260523171652123456001",
"block_transaction_id": "0xabc123...",
"status": 2
},
"request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}TON accepts canonical ton:<receive_raw>:<lt>:<hash>, lt:hash, or a unique recent hash-only reference for the order receive address. Aptos accepts a transaction hash.
Switch Payment Network/Channel
POST /pay/switch-network
This endpoint is usually called by the cashier frontend to switch to another on-chain receiving address, or to switch to the OkPay hosted cashier.
Switch Network Request Example
{
"trade_id": "20260523171652123456001",
"token": "USDT",
"network": "solana"
}Switch to OkPay:
{
"trade_id": "20260523171652123456001",
"token": "USDT",
"network": "okpay"
}Switch Network Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
trade_id | string | Yes | Parent order transaction number. |
token | string | Yes | Target token. |
network | string | Yes | Target network, or the special value okpay. |
Switch Network Success Response
Returns the same structure as cashier initialization data. For on-chain orders, payment_url is empty; for OkPay orders, payment_url is the hosted payment link returned by OkPay. If the parent order is still status=4, the first switch to on-chain or OkPay completes the parent order in place and returns the same trade_id.
Notes:
- Only parent orders can switch networks; child orders cannot be switched again.
- The parent order must be waiting for payment with status
1, or be a placeholder with status4. - The first concrete chain/token selection for status
4completes the parent order in place and returns the sametrade_id; it does not create a child order. - The first
network=okpayselection for status4does not require the parent order to already have on-chain fields; the system completes the parent as an OkPay order in place and returns the sametrade_idplus the OkPaypayment_url; it does not create a child order. - After status
4is completed, the order becomes status1butis_selectedremainsfalse; a later same-target selection returns the parent and marks it selected, while switching to another payment target creates a child order. - Each parent order can create at most one child order. After a child order has been created, the parent cannot be used to create a second new child order. Child orders themselves cannot switch networks.
- If switching to the same
token + networkcombination, the existing order is returned.
Create an EPay-Compatible Transaction
GET /payments/epay/v1/order/create-transaction/submit.php
POST /payments/epay/v1/order/create-transaction/submit.php
This endpoint is compatible with traditional EPay/YiPay integration flows. On success, it does not return JSON. It responds with HTTP 302 and redirects to:
/pay/checkout-counter/{trade_id}EPay Request Parameters
| Field | Location | Type | Required | Description |
|---|---|---|---|---|
pid | query/form | string | Yes | Merchant PID. Numeric PID is recommended; EPay callbacks output PID as a number. |
money | query/form | number | Yes | Fiat amount. |
out_trade_no | query/form | string | Yes | Merchant order number. |
notify_url | query/form | string | Yes | Asynchronous callback URL. |
return_url | query/form | string | No | Synchronous redirect URL after payment completion. |
name | query/form | string | No | Product or order name. |
type | query/form | string | No | alipay or a valid token.network selector, such as usdt.tron. A valid selector determines the actual on-chain token and network and overrides EPay default token/network. |
token | query/form | string | No | Optional payment token. It has higher priority than backend epay.default_token; if sent, it must participate in EPay signing. |
network | query/form | string | No | Optional payment network. It has higher priority than backend epay.default_network; if sent, it must participate in EPay signing. |
currency | query/form | string | No | Optional fiat currency. It has higher priority than backend epay.default_currency; if sent, it must participate in EPay signing. |
sign | query/form | string | Yes | EPay signature. |
sign_type | query/form | string | No | Usually MD5. |
Signature rules:
- Use the
secret_keythat corresponds topid. - Exclude
signandsign_type. - Join all other non-empty parameters in ASCII key order, append
secret_key, and MD5 the result. If the integration plugin sends extra fields such assitename, those fields must also participate in signing.
Example string to sign:
money=100&name=VIP¬ify_url=https://merchant.example/notify&out_trade_no=ORD202605230001&pid=1000&return_url=https://merchant.example/return&type=alipayepusdt_secret_keyResult:
sign=b865b0acbb2b01554c35a1bd33351452EPay type/token/network/currency resolution priority:
type=token.network: if it is a currently available on-chain combination, such asusdt.tron, it determinestoken/networkfirst. Iftypeis neither a valid selector noralipay, the API returns a parameter error.token/network: if no validtypeselector is used, request parameters are read. Explicitly submitted fields must participate in EPay signing.epay.default_token/epay.default_network: used when the request does not provide the corresponding fields. A validtypeselector bypasses these two defaults.currency: requestcurrency> databaseepay.default_currency>cny; this fallback rule is unchanged even when a validtypeselector is used.- If final
token/networkboth have values, a concrete on-chain order is created. If both are empty, a status4placeholder order is created. If only one is missing, the API returns a parameter error. - After the EPay signature passes, the server internally injects
payment_type=Epay. That field does not participate in the inbound EPay signature. However, explicitly submittedtype/token/network/currencyare original EPay parameters and must participate in signing.
Backend defaults can be inspected through the epay field in /payments/gmpay/v1/config. A new installation only seeds epay.default_currency=cny; epay.default_token and epay.default_network are empty, so EPay requests without explicit token/network create status 4 placeholder orders. Existing database configuration is not overwritten by seeds. After deleting or emptying epay.default_token and epay.default_network, these two fields return empty strings.
Direct Token/Network Selection
Pass type=token.network to directly create an order for a specific on-chain asset. This is useful when an upstream “New API” or custom payment-method list exposes each chain/token combination as an independent payment option.
Example payment-method configuration:
[
{
"color": "rgba(var(--semi-blue-5), 1)",
"name": "GM Pay",
"type": "custom1"
},
{
"color": "rgba(var(--semi-blue-5), 1)",
"name": "GM Pay usdt.binance",
"type": "usdt.binance"
},
{
"color": "rgba(var(--semi-blue-5), 1)",
"name": "GM Pay usdt.tron",
"type": "usdt.tron"
}
]In this example:
type=usdt.binanceis parsed directly astoken=usdtandnetwork=binance.type=usdt.tronis parsed directly astoken=usdtandnetwork=tron.- These
typevalues must be currently availabletoken.networkcombinations on the server. Usesupported_assetsfrom/payments/gmpay/v1/configas the source of truth. custom1is not an Epusdt on-chain selector. If the upstream uses it as a generic “GM Pay” entry, the upstream plugin should map it to notype, totype=alipay, or to a normal GMPay placeholder-order flow. Do not submitcustom1unchanged to EPaysubmit.php, or the current server rejects it as a parameter error.
When submitting to EPay submit.php, type is an original inbound parameter and must participate in the EPay signature.
Merchant Asynchronous Callback
After an order is paid successfully, Epusdt sends an asynchronous notification to the order's notify_url. The target server must return HTTP 200 with response body ok or success case-insensitively. Otherwise, Epusdt retries according to queue configuration: after the first failure, it retries up to order_notice_max_retry times, using exponential backoff based on callback_retry_base_seconds, capped at 5 minutes.
GMPay Callback
Normal GMPay orders use a POST JSON callback.
{
"pid": "1000",
"trade_id": "20260523171652123456001",
"order_id": "ORD202605230001",
"amount": 100,
"actual_amount": 14.29,
"receive_address": "TTestTronAddress001",
"token": "USDT",
"block_transaction_id": "0xabc123...",
"signature": "a1b2c3d4e5f6...",
"status": 2
}| Field | Type | Description |
|---|---|---|
pid | string | PID of the API key that owns the order. Merchants should use this PID to find the local key and verify the signature. |
trade_id | string | Epusdt transaction number. |
order_id | string | Merchant order number. |
amount | number | Fiat amount submitted by the merchant. |
actual_amount | number | Actual crypto amount received. |
receive_address | string | Receiving address. |
token | string | Payment token. |
block_transaction_id | string | On-chain transaction hash or third-party payment order number. |
signature | string | Callback signature. |
status | integer | Currently callbacks are sent only for successful payments; the value is 2. |
GMPay callback signature verification is the same HMAC-SHA256 rule as order creation, excluding the signature field.
EPay-Compatible Callback
Orders created through the EPay-compatible API use a GET request to callback notify_url with these parameters:
EPay callbacks output
pidas a number. When using the EPay-compatible API orpayment_type=Epay, make sure the API key PID is numeric.
pid=1000
trade_no=20260523171652123456001
out_trade_no=ORD202605230001
type=alipay
name=VIP
money=100.0000
trade_status=TRADE_SUCCESS
sign=a1b2c3d4...
sign_type=MD5For verification, exclude sign and sign_type, join all other non-empty parameters in ASCII key order, append secret_key, and MD5 the result.
OkPay Platform Callback
POST /payments/okpay/v1/notify
This is the endpoint where the OkPay/OkayPay platform notifies Epusdt. It is not an endpoint that merchant systems call proactively. When configuring OkPay, set the callback URL to this path.
It supports JSON, application/x-www-form-urlencoded, multipart form, and raw query-string style bodies. Success returns plain text:
successFailure returns HTTP 400:
failEpusdt verifies the OkPay signature with the configured OkPay shop token. After verification succeeds, it marks the matching OkPay order as paid and triggers the merchant callback. This OkPay order may come from completing a status 4 placeholder parent order in place, or from a child order created by a later switch.
status_code Values
| Status Code | HTTP Status | Description |
|---|---|---|
200 | 200 | Success |
400 | 400 | System error, or general parameter/validation error |
401 | 401 | Signature authentication error |
10001 | 400 | Wallet address already exists |
10002 | 400 | Payment transaction already exists; do not create it repeatedly |
10003 | 400 | No available wallet address; unable to start payment |
10004 | 400 | Invalid payment amount; unable to satisfy minimum payment unit |
10005 | 400 | No available amount channel |
10006 | 400 | Exchange-rate calculation error |
10007 | 400 | Order block already processed |
10008 | 400 | Order does not exist |
10009 | 400 | Unable to parse parameters |
10010 | 400 | Order status has changed |
10011 | 400 | Child order count limit exceeded |
10012 | 400 | Cannot switch network for a child order |
10013 | 400 | Order is not waiting for payment |
10014 | 400 | Chain is not enabled |
10016 | 400 | Supported asset does not exist |
10017 | 400 | Payment provider is not enabled |
10018 | 400 | Payment provider configuration is incomplete |
10019 | 400 | Payment provider does not support this token or network |
