USDT Payment Acceptance Setup
USDT exists in several incompatible versions across different networks, and the first question to settle: which blockchain to accept on. This is not a technical choice — it's a business decision with direct conversion consequences. Most retail users hold USDT on TRC-20 (Tron) due to minimal fees (~0.3–1 USDT per transfer). Corporate clients and exchanges — predominantly ERC-20 (Ethereum) or BEP-20 (BNB Chain). If you don't know your audience — support at least TRC-20 and ERC-20.
USDT Technical Specifics
USDT (Tether) — an ERC-20 token with non-standard behavior, often forgotten:
-
Doesn't return bool from
transfer()on Ethereum. Standard ERC-20 should returnbool, USDT doesn't. If you write a smart contract accepting USDT, useSafeERC20.safeTransfer()from OpenZeppelin — it handles both cases. - Has a blacklist — Tether can freeze an address. This is a real risk for custody solutions.
- Has fee mechanism (historically not used, but the function exists in the contract) — theoretically Tether can enable transfer fees.
Payment Acceptance Architecture
Option 1: Unique Address Per Payment (Recommended)
Generate HD wallet (BIP-44), derive a new address for each order. User transfers to this address — we listen for incoming transactions via blockchain node or API.
Master seed → BIP-44 path m/44'/195'/0'/0/N → address for order N
Advantages: full privacy between users, no shared state, easy payment reconciliation. Disadvantage: need to store derivation index and listen to addresses.
For TRC-20 (Tron): derivation path m/44'/195'/0'/0/N, addresses in base58check format starting with T.
Option 2: Single Address with Memo/Comment
One receiving address, user specifies unique identifier in memo. Simpler to implement, worse UX — users forget memos.
Monitoring Incoming Transactions
Two approaches depending on infrastructure:
Via Third-Party API (fast, with dependency):
- TronGrid API (
https://api.trongrid.io) for TRC-20 - Alchemy/Infura webhooks for ERC-20 — subscribe to
Transferevents of USDT contract
// Example webhook handler for Alchemy (ERC-20 USDT)
app.post('/webhook/alchemy', (req, res) => {
const { event } = req.body
if (event.eventName === 'Transfer') {
const { to, value } = event.activity[0]
// value in wei, divide by 10^6 (USDT has 6 decimals, not 18)
const amount = BigInt(value) / BigInt(1_000_000)
processPayment(to, amount)
}
res.sendStatus(200)
})
Important: USDT uses 6 decimals, not 18. This causes bugs for developers accustomed to ETH.
Via Own Node (reliable, more expensive):
For ERC-20: subscribe to eth_subscribe("logs") with filter by USDT contract address and Transfer(address,address,uint256) topic.
const USDT_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
provider.on({ address: USDT_ADDRESS, topics: [TRANSFER_TOPIC] }, (log) => {
const to = '0x' + log.topics[2].slice(26) // decode indexed address
const amount = BigInt(log.data) / BigInt(1_000_000)
// check 'to' against our payment-awaiting addresses
})
Transaction Confirmations
| Network | Recommended Confirmations | Wait Time |
|---|---|---|
| Ethereum (ERC-20) | 12–20 blocks | ~3–5 minutes |
| Tron (TRC-20) | 20 blocks | ~1 minute |
| BNB Chain (BEP-20) | 15 blocks | ~45 seconds |
For payments under $1000 you can reduce to 3–6 confirmations. For large amounts — wait for full finality.
Storing Private Keys
Private keys from receiving addresses cannot be stored in database in plaintext. Minimally acceptable — AES-256 encryption with key from environment variables. Proper solution — HashiCorp Vault or AWS KMS for master seed, derive keys on-the-fly.
Sweep strategy: automatically transfer received funds from receiving addresses to cold wallet after each payment. Don't accumulate on hot addresses more than daily turnover.
What's Included
- Selection and configuration of network (TRC-20, ERC-20, BEP-20 or multiple)
- HD wallet generation, address derivation setup
- Integration with blockchain API or node deployment for monitoring
- Webhook or polling service for processing incoming transactions
- Confirmation logic and reconciliation with orders
- Basic key protection and sweep automation







