Merchant Console Home

UPG API Integration Guide

UPG (USDT Pay Gateway) is a non-custodial on-chain USDT payment gateway. Merchants create payment orders via the API; funds are sent directly to the merchant's configured wallet address — the platform never holds funds.

Before you begin: In the Merchant Console → API Settings, configure your TRC20 / BEP20 / ERC20 receiving addresses and obtain your API Key and API Secret.

API Basics

Base URLhttps://your-domain.com
Request FormatJSON body (Content-Type: application/json)
Response FormatJSON envelope: {"code":200,"message":"ok","data":{...}}
Character EncodingUTF-8
Supported ChainsTRC20 (TRON) / BEP20 (BSC) / ERC20 (ETH)

Quick Start

The example below shows how to create your first USDT payment order and handle the callback in about 5 minutes.

PHP
Python
Node.js
// 1. Build signature
$params = [
    'api_key'          => 'YOUR_API_KEY',
    'merchant_order_no' => 'ORDER_' . time(),
    'amount'           => 100.00,
    'currency'         => 'USDT',
    'chain'            => 'TRC20',
    'notify_url'       => 'https://your-site.com/notify',
    'timestamp'        => time(),
    'nonce'            => bin2hex(random_bytes(8)),
];
ksort($params);
$parts = [];
foreach ($params as $k => $v) {
    if ($v === '' || $v === null) continue;
    $parts[] = $k . '=' . $v;
}
$signStr = implode('&', $parts); // api_secret is the HMAC key only, not appended to the string
$params['sign'] = hash_hmac('sha256', $signStr, 'YOUR_API_SECRET');

// 2. Send request
$ch = curl_init('https://your-domain.com/api/pay/create');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($params),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
]);
$resp = json_decode(curl_exec($ch), true);

// 3. Handle response
if ($resp['code'] === 200) {
    $order  = $resp['data'];
    // Redirect user to checkout page
    header('Location: ' . $order['pay_url']);
}
import hmac, hashlib, time, random, string, requests

api_key    = 'YOUR_API_KEY'
api_secret = 'YOUR_API_SECRET'

params = {
    'api_key':          api_key,
    'merchant_order_no': f'ORDER_{int(time.time())}',
    'amount':           100.00,
    'currency':         'USDT',
    'chain':            'TRC20',
    'notify_url':       'https://your-site.com/notify',
    'timestamp':        int(time.time()),
    'nonce':            ''.join(random.choices(string.ascii_lowercase, k=16)),
}
sorted_params = dict(sorted(params.items()))
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params.items() if v != '' and v is not None])
# api_secret is the HMAC key only, not appended to the string
params['sign'] = hmac.new(api_secret.encode(), sign_str.encode(), hashlib.sha256).hexdigest()

resp = requests.post('https://your-domain.com/api/pay/create', json=params, timeout=10)
data = resp.json()
if data['code'] == 200:
    print('Checkout:', data['data']['pay_url'])
const crypto = require('crypto');
const axios  = require('axios');

const API_KEY    = 'YOUR_API_KEY';
const API_SECRET = 'YOUR_API_SECRET';

const params = {
  api_key:          API_KEY,
  merchant_order_no:`ORDER_${Date.now()}`,
  amount:           100.00,
  currency:         'USDT',
  chain:            'TRC20',
  notify_url:       'https://your-site.com/notify',
  timestamp:        Math.floor(Date.now() / 1000),
  nonce:            crypto.randomBytes(8).toString('hex'),
};
// api_secret is the HMAC key only, not appended to the string
const signStr = Object.keys(params).sort()
  .filter(k => params[k] !== '' && params[k] != null)
  .map(k => `${k}=${params[k]}`).join('&');
params.sign = crypto.createHmac('sha256', API_SECRET).update(signStr).digest('hex');

const { data } = await axios.post('https://your-domain.com/api/pay/create', params);
if (data.code === 200) console.log('Checkout:', data.data.pay_url);

Authentication

All business endpoints (create order, query order, etc.) require HMAC-SHA256 signature authentication. Each request must include the following common parameters:

ParameterTypeRequiredDescription
api_keystringRequiredMerchant API Key, obtained from API Settings in the console
timestampintRequiredCurrent Unix timestamp (seconds); server allows ±300 seconds drift
noncestringRequiredRandom string, ≥8 characters; must not be reused within 15 minutes (replay protection)
signstringRequiredHMAC-SHA256 signature, lowercase hex; see next section for algorithm
Idempotent processing: Network jitter or manual resend may deliver the same order multiple times. Check whether the order has already been processed locally; use merchant_order_no or tx_hash for idempotency.
Manual resend: In the Merchant Console, Payment Records → Resend triggers a callback synchronously (order must be paid and authorization valid). Manually changing status does not trigger a callback automatically.
Service fee: In pay-as-you-go mode, fees are calculated from the order's USDT amount and deducted only on the first successful callback for that order; retries and duplicate resends are not charged again. Monthly plans do not deduct credits.

Signature Algorithm

Collect all request parameters (excluding sign itself)
Merge common parameters (api_key, timestamp, nonce) with business parameters into a key-value object. Exclude fields with empty string or null values.
Sort parameter names in ascending ASCII order
ksort($params) or Object.keys(params).sort()
Build the signature string
Join sorted parameters as key=value pairs with &, skipping empty values. Do not append api_secret to the string — api_secret is used only as the HMAC key in the next step.
Compute HMAC-SHA256
Use api_secret as the key, compute HMAC-SHA256 over the string above, and convert the result to a lowercase hex string — that is the value of sign.

Signature Example

Parameters: api_key=abc, amount=100, nonce=xyz123, timestamp=1700000000

# Sorted and joined (lexicographic order):
api_key=abc&amount=100&nonce=xyz123×tamp=1700000000
# This is the complete signature string; api_secret is not in the string, only used as HMAC key

# PHP:
$sign = hash_hmac('sha256', $signStr, 'YOUR_SECRET');

# Python:
sign = hmac.new(secret.encode(), sign_str.encode(), hashlib.sha256).hexdigest()

# Node.js:
sign = crypto.createHmac('sha256', secret).update(signStr).digest('hex');

Create Payment Order

POST /api/pay/create Signature required

Create a USDT payment order. If an unpaid, non-expired order with the same merchant_order_no already exists, the original order is returned (idempotent).

Billing note: Order creation validates API Key and signature only — it does not check authorization or credits. If authorization has expired or credits are insufficient, a payment QR code may still be generated, but on-chain payments may not be monitored and callbacks may not be sent.

Request Parameters

ParameterTypeRequiredDescription
merchant_order_nostringRequiredUnique merchant order ID, ≤64 characters
amountnumberRequiredPayment amount, >0
currencystringOptionalFiat currency: CNY (default) / USD / USDT
chainstringOptionalPayment chain: TRC20 / BEP20 / ERC20. Official shop plugins and the general SDK example omit this; chain selection is handled on the UPG checkout page. Advanced integrations may specify a chain.
notify_urlstringOptionalAsync callback URL; overrides default configured in console
return_urlstringOptionalRedirect URL after successful payment on checkout page
langstringOptionalCheckout language: en (default, /pay.html) / zh (/zh-cn/pay.html); alias locale; included in signature
attachstringOptionalPass-through parameter, returned as-is in callback, ≤500 characters
api_key / timestamp / nonce / signRequiredCommon auth parameters; see Authentication

Response data

FieldTypeDescription
order_nostringSystem order ID, format UPG + timestamp + random
merchant_order_nostringMerchant order ID (echoed back)
chainstring|nullPayment chain
chain_pendingboolWhether chain selection is pending (currently always false for create)
pay_addressstring|nullUSDT receiving address
usdt_amountstringExact USDT amount to transfer
fiat_amountnumberFiat amount
fiat_currencystringFiat currency code
exchange_ratefloatExchange rate snapshot at order time (1 USDT = X fiat)
pay_urlstringCheckout page URL (lang=en/pay.html, lang=zh/zh-cn/pay.html); redirect users here to pay
pay_localestringCheckout language: en or zh
return_urlstringRedirect URL after successful payment
qr_urlstringQR code data URL
expire_atstringOrder expiry time Y-m-d H:i:s
expire_secondsintSeconds until expiry
statusint0 = Pending payment
status_textstringHuman-readable status description

Response Example

{
  "code": 200,
  "message": "Order created successfully",
  "data": {
    "order_no":         "UPG20240101120000A1B2C3",
    "merchant_order_no": "ORDER_1704067200",
    "chain":            "TRC20",
    "pay_address":      "TNVxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "usdt_amount":      "100.000000",
    "fiat_amount":      100,
    "fiat_currency":    "USDT",
    "exchange_rate":    1,
    "pay_url":          "https://your-domain.com/pay.html?order=UPG20240101120000A1B2C3",
    "pay_locale":       "en",
    "qr_url":           "https://your-domain.com/pay/qr?order=UPG...",
    "expire_at":        "2024-01-01 12:30:00",
    "expire_seconds":   1800,
    "status":           0
  }
}

Query Order Status

GET /api/pay/query Signature required

Query the current status of an order by system order ID or merchant order ID.

Request Parameters

ParameterTypeRequiredDescription
order_nostringOne ofSystem order ID
merchant_order_nostringOne ofMerchant order ID
api_key / timestamp / nonce / signRequiredCommon auth parameters

Response data

FieldTypeDescription
order_nostringSystem order ID
merchant_order_nostringMerchant order ID
statusintSee Order Status Codes
status_textstringHuman-readable status description
chainstringPayment chain
pay_addressstringReceiving address
usdt_amountstringExpected USDT amount
actual_amountstringActual USDT paid (filled after on-chain confirmation)
fiat_amountnumberFiat amount
fiat_currencystringFiat currency code
exchange_ratefloatExchange rate snapshot at order time
tx_hashstringOn-chain transaction hash
from_addressstringPayer address
confirmationsintBlock confirmations
paid_atstring|nullPayment time
expire_atstringExpiry time
created_atstringCreation time

Query Available Chains

GET /api/pay/chains Signature required (authorization/credits not checked)

Query payment chains configured for the current merchant. Optionally pass an amount for billing pre-check. Recommended before creating orders to avoid requests that will fail.

Request Parameters

ParameterTypeRequiredDescription
amountnumberOptionalUsed with currency for billing pre-check
currencystringOptionalCNY / USD / USDT, default USDT
api_key / timestamp / nonce / signRequiredCommon auth parameters

Response data

FieldTypeDescription
chainsarrayConfigured chains with addresses, e.g. ["TRC20","BEP20"]
chain_optionsarrayPer-chain details: chain, enabled, reason, notice
billing_activeboolWhether billing status is healthy
billing_noticestringNotice when billing is abnormal

Callback Specification

On-chain payment
UPG Monitor
POST your notify_url
Return OK
Important: Your server must respond with HTTP 200 and body OK within 10 seconds, or the system will retry with exponential backoff, up to 5 times.

When Callbacks Are Sent

ScenarioBehavior
On-chain payment matched to orderQueued immediately, async POST to notify_url
Manually marked paid in consoleNo automatic callback; use Resend in Payment Records
Authorization expired / insufficient creditsCallback skipped; renew and resend manually from console

Callback Parameters

ParameterTypeDescription
order_nostringSystem order ID
merchant_order_nostringMerchant order ID
statusint1 = Paid (callbacks are only sent on successful payment)
chainstringPayment chain
pay_addressstringReceiving address
usdt_amountstringExpected USDT amount
actual_amountstringActual USDT paid on-chain
fiat_amountnumberFiat amount
fiat_currencystringFiat currency code
tx_hashstringOn-chain transaction hash, globally unique
paid_atstringOn-chain payment time
attachstringMerchant pass-through parameter (echoed back)
timestampintCallback send timestamp
signstringCallback signature; verify with notify_secret

Verify Callback Signature (PHP Example)

$notifySecret = 'YOUR_NOTIFY_SECRET'; // Obtain from API Settings in Merchant Console
$data = json_decode(file_get_contents('php://input'), true);

// Extract sign; remaining parameters participate in signature
$sign  = $data['sign'];
unset($data['sign']);

// Sort by key ascending; notify_secret is HMAC key only
ksort($data);
$parts = [];
foreach ($data as $k => $v) {
    if ($v === '' || $v === null) continue;
    $parts[] = $k . '=' . $v;
}
$signStr  = implode('&', $parts); // notify_secret is HMAC key only
$expected = hash_hmac('sha256', $signStr, $notifySecret);

if (!hash_equals($expected, $sign)) {
    http_response_code(400);
    exit('INVALID SIGN');
}

// Process business logic: update order status
if ((int)$data['status'] === 1) {
    // TODO: Mark order as paid, fulfill order, grant access, etc.
    updateOrderStatus($data['merchant_order_no'], 1);
}

echo 'OK'; // Must return OK or the system will retry
Idempotent processing: Network jitter or manual resend may deliver the same order multiple times. Check whether the order has already been processed locally; use merchant_order_no or tx_hash for idempotency.
Billing restriction: If authorization has expired or credits are insufficient, callbacks may be skipped even after on-chain payment. Top up and resend manually from the console.

Verify Callback Signature (Helper Endpoint)

POST /api/pay/verify-callback Signature required

Submit received callback data to this endpoint for gateway-side verification of the notify_secret signature. Useful during debugging; in production, verify locally (see previous section).

Request Parameters

ParameterTypeRequiredDescription
signstringRequiredCallback sign value to verify
(other callback fields)mixedRequiredPass all other fields from the full callback body as-is
api_key / timestamp / nonce / signRequiredCommon auth parameters (note naming conflicts with callback fields)

Response Data

FieldTypeDescription
validbooltrue = signature valid / false = signature invalid

Order Status Codes

StatusMeaningDescription
0 PendingAwaiting transferOrder is valid; no on-chain payment detected
1 PaidPayment successfulOn-chain payment confirmed; callback sent or pending
2 ExpiredOrder expiredNo payment received within validity period (default 30 minutes)
3 AbnormalRisk flaggedRisky transaction detected; requires manual review

Error Codes

HTTP StatuscodeCommon Causes
400400Missing or invalid parameters (e.g. negative amount, order ID too long)
401401Authentication failed: invalid api_key, bad signature, timestamp expired, nonce reused
403403Account disabled, authorization expired, or not activated (query and other fully authenticated endpoints; create order does not check authorization)
404404Order not found (or does not belong to current merchant)
429429Rate limit exceeded (default 60 requests/minute)
500500Internal server error; contact administrator

Error Response Format

{
  "code": 401,
  "message": "Signature verification failed",
  "data": null
}

SDK Downloads

Choose the SDK package for your platform, extract it, and follow the README to integrate. All packages include bundled dependencies — no extra installation required.

PHP
General PHP SDK
Works with ThinkPHP, Laravel, CodeIgniter, vanilla PHP, and more. Includes order creation and callback handling examples.
Download
WordPress
WooCommerce Plugin
For WordPress + WooCommerce stores. Extract and upload to /wp-content/plugins/, then activate.
Download
WordPress
RiPro Theme Plugin
For WordPress + RiPro theme native payments. No WooCommerce required. Configure under Settings → UPG RiPro Payment.
Download
Dujiaoka
Dujiaoka Plugin
For Dujiaoka systems. Place the upg folder in app/Payments/ and configure in the admin panel.
Download
Download links require a logged-in merchant account.

FAQ

Q: How do I choose a payment chain?

Official WooCommerce, RiPro, and Dujiaoka plugins, plus the general SDK example, do not pass chain when creating orders — selection is handled on the UPG checkout page. Advanced API integrations may pass TRC20 / BEP20 / ERC20 to pre-lock a chain; if omitted, the default is auto-selected by TRC20→ERC20→BEP20 priority and users can still switch on checkout. TRC20 has the lowest fees and fastest confirmation.

Q: What if the user overpays or underpays?

The system identifies on-chain payments uniquely by tx_hash. If the actual amount does not match the expected amount, the order is marked as status 3 (Abnormal) and requires manual handling in the admin console.

Q: I never received a callback — what should I do?

1. Confirm notify_url is publicly reachable and returns OK; 2. Check that authorization/credits are valid; 3. Callbacks are usually sent automatically after on-chain payment — if missing, use Resend in Payment Records (manually marking paid does not trigger a callback); 4. If resend fails, check the error message returned.

Q: What happens if I submit a duplicate merchant order ID?

If the existing order is still pending and not expired, the API returns the original order data (idempotent) without creating a duplicate. If the old order has expired or been paid, a new order is created.

Q: Signature verification always fails — why?

Common causes: ① Empty-value fields included in the signature string (should be skipped); ② Number serialization adds extra decimals or spaces; ③ api_secret does not match api_key; ④ Server clock drift exceeds 5 minutes from standard time.

For additional help, contact the platform administrator or use the Checkout Test page in the Merchant Console to verify your signature implementation.