Asset

How to accept USDT payments online

Most customers who say they'll "pay in crypto" mean USDT, and the practical question for a merchant is not whether to take it but how — on which network, at what amount, and how it lands in your books. The answer with halfin is one fiat-anchored invoice: you bill a dollar figure, the customer picks Tron (TRC-20), Ethereum (ERC-20) or Solana at the point of payment, and the amount that settles reconciles back to the dollars you charged. This page walks the acceptance flow end to end and shows how to let the network be the customer's choice, not yours.

01

Let the customer pick the network

USDT is issued on several chains, and halfin accepts it on the three that matter for payments — Tron, Ethereum and Solana. The mistake is to hard-code one. A customer who holds USDT on Tron cannot pay you on Ethereum, and forcing a network you happen to prefer is how a ready-to-pay customer becomes an abandoned checkout.

The way around that is to not pin a network when you create the invoice. You bill in fiat and leave the asset open; halfin presents the supported USDT networks at the point of payment and lets the payer settle from wherever they already hold the token. One invoice covers all three rails, and every one of them credits to the same dollar-denominated balance, so you reconcile once rather than three times.

TRC-20 — USDT on Tron — is the rail most payers default to, because Tron's network fees are low and its throughput is high, which keeps a small stablecoin transfer cheap to send. That is worth knowing because for small tickets the network fee, not your price, is often what decides whether a customer completes the payment. You still offer all three; you just shouldn't be surprised when most stablecoin traffic arrives over TRC-20.

02

Pick the network by the job, not the brand

The three networks carry the same dollar token, but they don't behave the same at checkout. The table is the real supported surface for USDT on halfin — these three and no others — read by what each one is good for, so you know what to expect rather than which to enforce.

NetworkToken standardBest forSettlement character
TronTRC-20Small tickets and fee-sensitive payers; the low-friction default.Reorg-aware credit at Tron's confirmation threshold.
EthereumERC-20EVM-native and institutional wallets that hold USDT on mainnet.Reorg-aware credit at Ethereum's confirmation threshold.
SolanaSPLPayers already in the Solana ecosystem wanting fast, low-fee settlement.Reorg-aware credit at Solana's confirmation threshold.
03

Bill in dollars, get paid in USDT

USDT tracks the dollar, but "tracks" is not "is" — the live USDT/USD rate sits very near one and moves in small amounts. halfin keeps you out of that. You create an invoice with a fiat amount and currency; halfin maps that figure to the payable USDT amount using the live rate and locks it when the invoice activates. The customer sees a concrete number to send; your ledger records the dollars you billed.

Because the quote is pinned at activation rather than at draft time, the amount the customer is shown does not drift while they open their wallet and confirm. Each invoice carries an expiry — if the payment window lapses, the invoice expires instead of silently re-pricing against a newer rate, and you decide whether to re-issue. If a customer underpays, for instance because an exchange skims a withdrawal fee on the way out, the shortfall is recorded against the invoice rather than stranded; an overpayment is recorded the same way, so the excess stays visible and accountable.

The net effect is that USDT behaves, in your books, like the dollars you priced in. You get the cross-border reach and speed of a stablecoin without inheriting the job of converting token amounts back to fiat at month-end.

  • Bill a USD (or other fiat) amount — you never quote a USDT figure by hand.
  • halfin locks the rate at activation, so the payable amount doesn't drift while the customer pays.
  • Underpaid and overpaid amounts are recorded against the invoice, not lost.
  • A lapsed invoice expires rather than re-pricing against a newer rate.
04

Accept USDT with one API call

Accepting USDT is not a USDT-specific integration. It is the standard invoicing flow: you post a fiat amount and currency to the public REST API with a scoped API key, and halfin returns a payable invoice you present through hosted checkout or render yourself. Leaving the asset unpinned is what lets the customer choose USDT on Tron, Ethereum or Solana at the point of payment.

The call below is minimal. The amount is a string — monetary values are strings end to end, never floats — and the currency is your fiat anchor. The idempotency_key field in the request body makes a retried request safe: the same key never creates a second invoice. The full request and response schema lives in the docs and the @halfin/sdk-merchant types; the point here is that one authenticated call produces an invoice a customer can settle in USDT on any of the three networks.

curl -X POST https://api.thehalfin.com/api/v1/invoices \
  -H "X-API-Key: $HALFIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_fiat": "75.00",
    "fiat_currency": "USD",
    "deferred": true,
    "description": "Order 4012 — USDT accepted",
    "idempotency_key": "00000000-0000-4000-8000-000000000002"
  }'

# No asset pinned, so the payer chooses USDT on Tron (TRC-20),
# Ethereum (ERC-20) or Solana at the point of payment. halfin locks
# the rate at activation and pins the payable USDT amount. Track
# state via signed webhook events and verify the HMAC signature
# before acting. Full response schema at docs.thehalfin.com.
05

Know a USDT payment is final

Accepting USDT is only useful if you know when the money has actually arrived. You learn that from webhooks, not by polling. halfin emits an HMAC-signed event for each step of the invoice lifecycle, and your endpoint reacts to them. Verify the signature before you take any business action — releasing goods, marking an order paid, sending a receipt — because an unsigned or mis-signed payload is not something to act on.

The events you care about for an inbound USDT payment are the ordinary invoice lifecycle. invoice.confirming fires once a payment is seen but not yet final; invoice.paid fires when the amount has held to the network's confirmation threshold and the dollar figure is settled. invoice.underpaid and invoice.overpaid tell you the amount didn't match exactly, so you can decide how to handle the difference, and invoice.expired tells you the window lapsed unpaid. Crediting is reorg-aware throughout, so invoice.paid means the figure actually stuck on the chain the customer chose.

  • invoice.confirming — a USDT payment is seen, not yet final.
  • invoice.paid — settled to the dollar figure at the network's confirmation threshold.
  • invoice.underpaid / invoice.overpaid — the amount didn't match; you decide how to reconcile.
  • invoice.expired — the payment window lapsed unpaid.
  • Always verify the HMAC signature before acting on any event.
06

After the payment: balance, payouts, refunds

Accepting USDT is the front door; the balance it produces is the rest of the platform. Paid USDT invoices accrue to a balance you can hold, convert or disburse. If you owe a stablecoin to a partner, affiliate or supplier, single and mass payouts send USDT back out on the same networks. If a customer is owed money back, refunds handle the return path on a supported rail.

A USDT balance on Tron doesn't lock you into USDT on Tron — balance conversion lets you rebalance into another asset or network as your treasury needs. And because the whole flow is fiat-anchored, the USDT you accept sits in the same ledger as USDC and the native chain assets, settled to the same dollar figures, rather than as separate pots you reconcile by hand.