← 목록

Accepting USDT on Tron without a payment processor: what we check before crediting

devto 2026-10-17 원문 보기 ↗


When we added crypto top-ups to VerifDoc, many customers in South Asia asked for one thing: USDT on Tron (TRC-20). It's what they hold on exchanges, and network fees are a few cents. Here's how we accept it without a payment processor, and the checks that stop us from crediting money that never arrived.

The shape of it

// Candidates: TRC-20 transfers into the wallet since the deposit started.
const url = `/v1/accounts/${wallet}/transactions/trc20?only_to=true&min_timestamp=${since}&contract_address=${USDT}&limit=50`;
const { data } = await tronGet<{ data: Trc20Transfer[] }>(url);
const candidates = data.filter((t) => t.to === wallet && t.type === "Transfer");

Check 1: the token is really USDT

Anyone can deploy a token named "Tether USD" with the symbol USDT and send it to you. Never match on the symbol; match the contract address (TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t for USDT on Tron mainnet), both in the list query and again in the receipt.

Check 2: confirm on the solidity node, from the receipt

The transfers list is an index and convenient, but it isn't proof. For every candidate we fetch the transaction info from the solidity node, which only knows confirmed blocks:

const info = await tronPost<TxInfo>("/walletsolidity/gettransactioninfobyid", { value: txid });
const ok =
  info.id === txid &&
  info.receipt?.result === "SUCCESS" &&
  info.log?.some(
    (l) =>
      l.address === usdtHex &&                           // emitted by the real USDT contract
      l.topics[0] === TRANSFER_TOPIC &&                  // Transfer(address,address,uint256)
      l.topics[2].endsWith(walletHex) &&                 // to our wallet
      BigInt("0x" + l.data) === expectedUnits,           // the amount, in 6-decimal units
  );

A failed transaction can still show up in some indexes. The receipt.result === "SUCCESS" check, plus reading the actual Transfer event, is what makes it real.

Check 3: validate addresses properly

Tron addresses are base58check: a 0x41 prefix, 20 bytes, and a 4-byte double-SHA256 checksum. We decode and verify the checksum before saving a wallet. A single wrong character gives an invalid checksum instead of a silently wrong address.

Check 4: never credit twice

Testing it

If you accept stablecoins yourself: match contracts, not symbols; trust receipts, not indexes; and make the credit idempotent.

We run VerifDoc (https://verifdoc.net/?utm_source=dev&utm_medium=article&utm_campaign=tron-usdt): second phone numbers in the browser, 5¢ email addresses and more, paid from one prepaid balance in USDT or USDC.