Integration guide
This guide shows how to add USDT payments to an existing website or app. The examples use plain HTTPS + JSON, so they work in any language; ready-made clients are available for PHP, Node.js and Python.
How the money flows
Your customer's wallet ──USDT──► customer's personal deposit address ──(automatic)──► gateway treasury
│
├─ deposit.detected (seen on chain)
├─ deposit.confirmed (account-configured confirmations confirmations → credit your customer)
└─ deposit.collected (swept to the treasury)
Your merchant balance += amount − platform fee → Settlements → your payout wallet
Key ideas:
- One address per customer, forever. Create it once, store it, and show it every time that customer wants to pay. Any amount sent to it is credited to that customer.
- Your system decides what a payment is for. The address identifies the *customer*, not an order. Credit the customer's account or wallet balance in your app, or match deposits to open orders yourself (see "Order-based checkout" below).
- Only
CONFIRMEDcounts. Never fulfil onDETECTEDorCONFIRMING.
Step 1: Configure your server
Store two secrets in your server environment (never in code or client apps):
GATEWAY_BASE_URL=https://api.toknex.space
GATEWAY_API_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GATEWAY_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Every request sends:
Authorization: Bearer $GATEWAY_API_KEY
Content-Type: application/json
Step 2: Create a customer and their deposit address
Call this when a customer registers, or the first time they open your "Pay with USDT" page. Use your own customer id as external_id: the call is safe to repeat, and returns the same customer and address again.
curl -X POST https://api.toknex.space/api/v1/users \
-H "Authorization: Bearer $GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-user-10025" \
-d '{"external_id":"10025","name":"Asha Patel","email":"[email protected]"}'
{
"id": 4821,
"external_id": "10025",
"name": "Asha Patel",
"email": "[email protected]",
"status": "ACTIVE",
"created_at": "2026-10-25T09:00:00+00:00",
"deposit_wallet": {
"user_id": 4821,
"external_id": "10025",
"network": "BSC",
"network_name": "BNB Smart Chain",
"chain_id": 56,
"token": "USDT",
"token_standard": "BEP-20",
"token_contract": "0x55d398326f99059fF775485246999027B3197955",
"deposit_address": "0x3fA1c6d1B0F5f1E2d6C4b9A8e7D3c2B1a0F9e8D7",
"deployed": false,
"status": "ACTIVE",
"minimum_deposit": "account-configured min deposit",
"required_confirmations": account-configured confirmations,
"qr_payload": "0x3fA1c6d1B0F5f1E2d6C4b9A8e7D3c2B1a0F9e8D7"
}
}
Save id (our user id) and deposit_wallet.deposit_address with your customer record. Everywhere in the API, {id} can be either our numeric id or your external_id.
Note: "deployed": false is normal. The address is a deterministic smart-contract address that can receive USDT immediately; its contract is created automatically when funds are first collected.
Step 3: Show the payment instructions
You have two options.
Option A: Your own page (full control)
Show the customer:
- the deposit address with a copy button, and a QR code of the address (use
qr_payload) - Network: BNB Smart Chain (BEP-20) and Token: USDT
- the minimum of account-configured min deposit USDT (smaller deposits are not credited)
- a clear warning: *"Send only USDT on BNB Smart Chain. Funds sent on another network may be lost."*
- the status of their recent deposits (from webhooks, or
GET /api/v1/users/{id}/deposits)
Suggested copy:
Send USDT (BEP-20) to your personal address below. Your balance updates automatically after account-configured confirmations network confirmations, usually within a minute. Only send USDT on BNB Smart Chain.
Option B: Hosted deposit page (no front-end work)
Create a one-time link and redirect the customer to it:
curl -X POST https://api.toknex.space/api/v1/users/10025/portal-link \
-H "Authorization: Bearer $GATEWAY_API_KEY"
{ "url": "https://api.toknex.space/portal/link/2f8Kq…", "expires_at": "2026-10-25T09:30:00+00:00" }
The link works once and expires after 30 minutes. Create a fresh one each time the customer clicks "Pay with USDT". The hosted page shows the address, QR code, network warnings and live deposit status.
Step 4: Receive webhooks and credit the customer
When a deposit reaches account-configured confirmations confirmations we POST a deposit.confirmed event to your webhook URL:
{
"id": "evt_7731",
"type": "deposit.confirmed",
"created": 1792915965,
"data": {
"id": 9177,
"user_id": 4821,
"external_id": "10025",
"deposit_address": "0x3fa1…",
"from_address": "0x8b2c…",
"tx_hash": "0x5d…",
"amount": "500.000000000000000000",
"amount_raw": "500000000000000000000",
"status": "CONFIRMED",
"credited": true,
"below_minimum": false
}
}
Your handler should:
- Verify the signature (see Webhooks). Reject anything that fails.
- De-duplicate on
data.id(the deposit id). The same deposit can be delivered more than once. - If
typeisdeposit.confirmedanddata.creditedistrue, creditdata.amountto the customer identified bydata.external_id, inside a database transaction. - Respond HTTP 200 within 10 seconds. Do slow work (emails, fulfilment) after responding, or in a queue.
Minimal PHP handler:
$raw = file_get_contents('php://input');
if (!preg_match('/t=(\d+),v1=([0-9a-f]{64})/', $_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '', $m)
|| abs(time() - (int) $m[1]) > 300
|| !hash_equals(hash_hmac('sha256', $m[1] . '.' . $raw, getenv('GATEWAY_WEBHOOK_SECRET')), $m[2])) {
http_response_code(401); exit;
}
$event = json_decode($raw, true);
if ($event['type'] === 'deposit.confirmed' && $event['data']['credited']) {
$db->beginTransaction();
// INSERT ... payments (gateway_deposit_id UNIQUE) -> skip if it already exists
// UPDATE customers SET balance = balance + :amount WHERE id = :external_id
$db->commit();
}
http_response_code(200);
Full, production-ready receivers: PHP · Node.js · Python.
Important: use string or decimal types for amounts (DECIMAL(36,18)in SQL,Decimalin Python, a big-number library in JavaScript). Never parse them into floating-point numbers.
Step 5: Reconcile (recommended)
Webhooks can be delayed if your server is down. Run a job every few minutes that pulls recent deposits and processes any you have not seen:
curl "https://api.toknex.space/api/v1/deposits?status=CONFIRMED&since_id=9100&limit=100" \
-H "Authorization: Bearer $GATEWAY_API_KEY"
Process each item exactly like a webhook (the same de-duplication on id), and store the highest id you have processed as the next since_id.
Order-based checkout
The address identifies a customer, not an order. For "pay this invoice" flows:
- Create or fetch the customer's address as above.
- Show the order amount and the address.
- When
deposit.confirmedarrives, add the amount to the customer's balance in your system. If the balance covers an open order, mark the order paid and deduct it. - Overpayments stay on the customer's balance for future orders; for underpayments, show the remaining amount.
This approach is robust when customers pay in several transfers, pay slightly too much because of exchange withdrawal fees, or pay late.
Deposit statuses
| Status | Meaning | What you do |
|---|---|---|
DETECTED | Transfer seen on chain, 0–1 confirmations | Show "payment detected" |
CONFIRMING | Waiting for account-configured confirmations confirmations | Show progress |
CONFIRMED | Final: credited to your balance | Credit the customer |
COLLECTION_PENDING / COLLECTION_SUBMITTED | Being swept to the treasury | Nothing |
COLLECTED | Swept to the treasury | Nothing |
REORGED | The block was replaced; we re-check it | Wait; it returns to DETECTED if re-mined |
FAILED | Could not be verified | Contact support if unexpected |
Checklist before launch
See the Testing & go-live checklist.
