How to Accept USDT Payments on Your Website
Pick the right USDT networks, connect a gateway by plugin or API, verify webhooks and handle the edge cases that cause most support tickets.

The simplest way to accept USDT payments on your website is through a crypto payment gateway: your site creates an invoice, the customer pays on a hosted page that shows a unique address and QR code, and the gateway notifies your server by webhook once the payment is confirmed. You never have to match transfers by hand, and you can switch on several USDT networks at once.
You can also post a wallet address and check payments yourself, which works for a handful of orders but breaks quickly. This guide walks through both options, then sets up the gateway route step by step: choosing networks, connecting a plugin or the API, verifying webhooks and handling the cases where the customer sends too little, too much or too late.
Three ways to accept USDT on a website
| Method | How it works | Good for | Weak spots |
|---|---|---|---|
| Static wallet address | You show one address and ask for a screenshot or transaction hash | Occasional manual sales | You can't tell which payment belongs to which order; no automation |
| Self-hosted processor | You run software that watches your own wallets | Teams that want full custody and can run servers | Setup and maintenance; stablecoin and network support depends on your setup |
| Payment gateway | Each order gets an invoice with its own deposit address; a webhook tells your site when it is paid | Most online stores, SaaS and digital goods | You pay a commission; funds sit on the gateway balance until you withdraw |
The static-address method fails at the first busy day. Two customers send 25 USDT within a minute and you can't tell who paid for what. A gateway solves this by giving every invoice its own address, so a payment is matched to an order automatically.
Step 1. Decide which USDT networks to accept
USDT exists on many blockchains, and they are not interchangeable. A customer's wallet or exchange decides which network they can send on, and the fee they pay depends heavily on it. These are rough third-party estimates of what one USDT transfer costs the sender, as of October 2026. Check a block explorer for current numbers.
| Network | Typical sender cost | Address looks like |
|---|---|---|
| TRON (TRC20) | About 6.5 TRX burned, or far less with energy (fee model) | Starts with T |
| BSC (BEP20) | About $0.05–0.30 | Starts with 0x |
| Ethereum (ERC20) | About $0.20–2 when quiet, much more at peaks | Starts with 0x |
| TON | About $0.005–0.05 | UQ... or EQ... |
| Solana | Under $0.01 | Base58 string |
A sensible default is TRC20 and BEP20, because many exchange users hold USDT there, plus ERC20 for larger payments from customers who keep funds on Ethereum. Add TON if your audience lives in Telegram and Solana if your customers already use it. A gateway lets you enable all of them without extra work, so the real question is what to show first on your payment page.
The networks per coin are listed on the USDT payments page, and the USDT TRC20 page covers the most common one in detail.
Step 2. Choose how you price
You have two options:
Price in USD (or another fiat currency). The invoice is for, say, $49, and the customer chooses USDT and a network on the payment page. This is the usual choice: your catalogue stays in dollars and customers who prefer BTC or USDC can still pay.
Price in USDT. The invoice is for exactly 49 USDT. Useful when you want to force a specific coin, for example for B2B invoices.
On Zypher, an invoice priced in a coin expires after 30 minutes. A fiat-priced invoice gives the customer 60 minutes to pick a coin, then 30 minutes to pay after choosing.
Step 3. Connect the gateway to your site
If you use a CMS
Check for a ready plugin. Zypher has plugins for WooCommerce, WHMCS, OpenCart, PrestaShop, 1C-Bitrix, XenForo, Invision Community, MyBB and phpBB. The flow is the same everywhere:
Create a store in the gateway dashboard and copy the API key and secret.
Install the plugin and paste the key and secret into its settings.
Copy the webhook URL the plugin shows you into your store settings in the gateway.
Place a real test order and pay it.
If you have a custom site
Use the API. With Zypher, POST /api/v1/invoice creates an invoice and GET /api/v1/invoice/:id reads it. Every request carries two headers:
X-API-Keywith your store's API keyX-API-Signwith a hex HMAC-SHA256 of the JSON body, keyed with your API secret (for GET requests, of an empty string)
Here is the signing in plain curl. The invoice fields (amount, currency, your order ID and so on) are described in the API docs, and the base URL is listed there too.
# Create an invoice
BODY=$(cat invoice.json) # invoice fields as described in the docs
SIGN=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')
curl -X POST "$API_BASE/api/v1/invoice" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Sign: $SIGN" \
--data "$BODY"
# Read an invoice: sign an empty string
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')
curl "$API_BASE/api/v1/invoice/$INVOICE_ID" \
-H "X-API-Key: $API_KEY" -H "X-API-Sign: $SIGN"Sign the exact string you send. If you build the JSON, sign it, and then let your HTTP client serialize the object again, the signature will not match.
Redirect the customer to the hosted payment page from the response. It shows the amount, network, QR code and a live countdown, and is available in 11 languages. You can set success and fail redirect URLs per store.
Step 4. Mark orders paid only from the webhook
The success redirect only tells you the customer clicked back to your site. The payment may still be confirming, or it may never arrive. Your source of truth is the webhook (or a status check through the API).
On Zypher, the webhook is a POST with a JSON body {event, data, timestamp}. The X-Webhook-Signature header is a hex HMAC-SHA256 of the raw body using your API secret, and X-Webhook-Event names the event. The event is invoice.paid, and data.status is either PAID or OVERPAID. If your endpoint doesn't respond successfully, delivery is retried, up to 5 attempts.
A Python (Flask) handler:
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
API_SECRET = b"your-api-secret"
@app.post("/webhooks/zypher")
def zypher_webhook():
raw = request.get_data() # raw bytes, before any JSON parsing
expected = hmac.new(API_SECRET, raw, hashlib.sha256).hexdigest()
given = request.headers.get("X-Webhook-Signature", "")
if not hmac.compare_digest(expected, given):
abort(401)
payload = request.get_json()
if payload["event"] == "invoice.paid" and payload["data"]["status"] in ("PAID", "OVERPAID"):
mark_order_paid(payload["data"]) # find the order by the invoice fields from the docs
return "", 200Make the handler idempotent. Because of retries, the same event can arrive twice, so check whether the order is already paid before you ship again or send a second email.
Step 5. Plan for the edge cases
Most support tickets about USDT payments come from four situations. Decide your policy before launch.
Underpaid invoices
A customer sends 48.20 instead of 49 because their exchange took its withdrawal fee out of the amount. Zypher treats 98% or more of the amount as paid. Below that the invoice is UNDERPAID, and the customer can top up the remainder; partial payments add up.
Overpaid invoices
Anything above 102% is marked OVERPAID. The order is paid, and you decide whether to refund the difference, keep it as credit or ignore small amounts. Write the policy into your terms.
Late payments
A customer starts paying, gets distracted and sends after the invoice has expired. On Zypher, a payment that arrives up to 30 minutes after expiry can still be matched to the invoice. After that, it becomes a support case.
Wrong network
A customer sends USDT over BEP20 to a TRC20 address. Between EVM networks (ERC20, BEP20, Base, Arbitrum) the 0x address is the same, so whoever holds the key can usually recover the funds. A transfer between a TRON T address and a 0x address usually can't be recovered. The best fix is prevention: show the network name next to the address and make customers pick it explicitly.
Step 6. Decide what happens to the money
Once payments come in, you have choices:
Keep USDT on your gateway balance and withdraw when you need to. Withdrawals pay the network fee.
Auto-convert every incoming payment to one coin. On Zypher, a store can automatically swap each payment, net of commission, into a chosen coin such as USDT, which helps if you also accept BTC or ETH.
Withdraw to an exchange if you need to sell. Exchanges set their own deposit and withdrawal rules, so check them first.
Zypher's commission is taken from the received amount: 1% to start, falling to 0.25% as 30-day paid volume grows, with no setup or monthly fees. The full schedule is on the pricing page.
Launch checklist
At least two USDT networks enabled (for example TRC20 and BEP20)
Network name shown clearly on the payment page and in order emails
Webhook signature verified against the raw body
Orders marked paid only on
invoice.paidwith statusPAIDorOVERPAIDHandler is idempotent and responds fast
Policy written for underpaid, overpaid and late payments
Real test payment made on a cheap network, plus one expired invoice
Success and fail redirect URLs set
Refund process defined (crypto refunds go to an address the customer gives you)
There is no sandbox on Zypher, so the test step uses real money. A couple of dollars on TRC20 or BEP20 is enough.
A note on rules and taxes
Accepting stablecoins may have accounting, tax or licensing consequences depending on your country and business. This guide is general information, not legal or tax advice. Check local rules, or ask an accountant who knows crypto.
If you are moving from another provider, our guide to Coinbase Commerce alternatives covers the migration side. For store-specific setups see crypto payments for ecommerce. When you are ready, open a store and create your first invoice.
FAQ
- Can I accept USDT on my website without a payment gateway?
Yes, by posting a wallet address and checking transfers yourself. It works for very low volume, but you can't reliably match payments to orders and nothing updates automatically. A gateway gives every invoice its own address and confirms payment by webhook.
- Which USDT network should a website accept?
Most merchants start with TRC20 and BEP20 because many exchange users hold USDT there and fees are low. ERC20 is worth adding for larger payments. TON and Solana make sense if your customers already use those ecosystems.
- How long does a USDT payment take to confirm?
It depends on the network. Zypher credits payments after a set number of confirmations, for example 10 on TRON, 5 on BSC, 6 on Ethereum, 8 on Solana and 1 on TON. In practice that takes from a few seconds to a few minutes.
- What if the customer sends less USDT than the invoice?
On Zypher, a payment of 98% or more of the amount counts as paid. Below that, the invoice is marked underpaid and the customer can send the rest. Partial payments are added together.
- Do I need a company to accept USDT payments?
Opening a Zypher store and getting API keys needs no documents. Whether you need a registered business, and how to report income, depends on your country. Check local rules before you start selling.