How to Accept USDT Payments on Your Website: A Step-by-Step Guide (2026)
Adding USDT payments to a website is no harder than a card integration; in most cases it is faster. In this guide we build a working flow from scratch with a payment gateway: from opening an account to verifying the webhook.
Why USDT?
USDT (Tether) is a stablecoin pegged to the US dollar. The biggest problem with accepting Bitcoin or Ether is price volatility: an order worth 100 dollars at checkout can be worth 93 dollars by the time you look at your wallet. USDT does not have that problem; 100 USDT is roughly 100 dollars today and tomorrow. That is why the large majority of commercial crypto payments worldwide are made in stablecoins, and above all in USDT.
USDT exists on several blockchains. The TRC-20 version on the TRON network is the most widely used for everyday payments because of its low transaction fees and confirmation in seconds. On Ethereum (ERC-20) network fees can reach tens of dollars at busy times; on TRON the cost usually stays under a few dollars. See What is TRC-20 USDT for the details.
How the flow works
With a crypto payment gateway there are three parties: your site (your server), the gateway and the customer. The sequence is:
- The customer clicks "Pay with crypto" on your site.
- Your server sends a create invoice request to the gateway (amount, callback URL).
- The gateway returns a TRON address dedicated to this invoice and a ready-made checkout page link.
- The customer scans the QR code or copies the address on the checkout page and sends USDT from their wallet.
- The gateway watches the chain; when the payment appears it sends a signed webhook to your server.
- Your server verifies the signature and completes the order.
The critical point: the create-invoice request is made from your server, not the browser. Your API key never reaches the client. Likewise the amount is decided on the server; an amount coming from the client is never trusted.
1. Account and API key
Create a free account in the KriptoGo dashboard and verify your email. Then enter your project name and site URL. There are three ways to verify the domain: a DNS TXT record, a meta tag on your home page, or a text file at /kriptogo-verify.txt. Once approved, the dashboard shows two values:
- apiKey: identifies your account when creating invoices.
- secretKey: used only to verify webhook signatures. It is never sent anywhere.
Store both as environment variables (KRIPTOGO_API_KEY, KRIPTOGO_SECRET_KEY). Never hard-code them or commit them to version control.
2. Create an invoice from your server
When the customer clicks the payment button, your server determines the amount from its own data (cart, order, plan price) and makes a single POST request:
const r = await fetch('https://kriptogo.com/api/create-invoice', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: process.env.KRIPTOGO_API_KEY, amount: 49.90, // USDT callbackUrl: 'https://yoursite.com/kriptogo-webhook' }) }); const invoice = await r.json(); // store invoice.invoice_id against the order, then redirect to invoice.checkout_url
The response contains invoice_id, address, checkout_url and expires_at. Save the invoice id against the order; you will match the webhook with it later. Invoices are valid for 10 minutes, so create the invoice right before redirecting, not when the cart page opens.
3. Redirect to the checkout page
Redirect the customer to checkout_url. The hosted page shows a QR code, the address and amount with copy buttons, a countdown and a clear warning that only the TRON network is accepted. It polls the status endpoint every few seconds and updates itself when the payment is seen. If you pass a returnUrl, the customer is sent back to your site afterwards. You can also embed the page in an iframe if you prefer to keep the customer on your domain.
4. Confirm with the webhook
Once the payment is confirmed on-chain, KriptoGo POSTs a JSON body to your callback URL with the X-Kriptogo-Signature header. The header is the HMAC-SHA256 of the raw request body, keyed with your secret key. Verify it before doing anything else:
app.use('/kriptogo-webhook', express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString('utf8'); } })); app.post('/kriptogo-webhook', async (req, res) => { const expected = crypto.createHmac('sha256', process.env.KRIPTOGO_SECRET_KEY).update(req.rawBody).digest('hex'); const got = req.get('X-Kriptogo-Signature') || ''; if (got.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) { return res.status(401).send('bad signature'); } res.sendStatus(200); // respond first const { invoice_id, status, paid_amount } = req.body; if (status === 'PAID') await orders.markPaidOnce(invoice_id, paid_amount); // idempotent });
Three rules make this safe: compute the signature over the raw body (parsing and re-serialising JSON can change byte order), compare with a constant-time function, and process each invoice id only once because delivery is retried up to three times. A deeper explanation is in the webhook security guide.
5. Test end to end
Create an invoice for a small amount, open the checkout page and pay from your own wallet. Confirm that the webhook arrives, that your order changes to paid, and that a second delivery of the same notification does nothing. Then send a request with a wrong signature and confirm it is rejected with 401. If your development machine has no public URL, poll GET /api/status/:id instead of the webhook while testing.
Common mistakes
- Calling the API from the browser. This leaks your API key. Always create invoices server-side.
- Trusting the amount from the client. A user can edit a hidden form field. Compute the amount on the server.
- Verifying the signature over parsed JSON. Use the raw body.
- Delivering twice. Retries mean duplicates; make the handler idempotent.
- Creating the invoice too early. A 10-minute invoice created on the cart page may expire before the customer pays.
- Ignoring paid_amount. A customer can underpay by a small margin; compare
paid_amountwith your own price before fulfilling high-value orders.
What does it cost?
With KriptoGo there is no setup or monthly fee; a 1% commission is charged only on successful payments. The customer pays only the TRON network fee from their wallet, which is typically well under a dollar for TRC-20 transfers. You withdraw your balance to your own TRON address whenever you like.
Frequently asked questions
Do I need to run a wallet or a node?
No. The gateway generates the addresses, watches the chain and holds the balance until you withdraw. You only make HTTP requests.
Can customers pay in TRX instead of USDT?
Yes. The checkout page shows the TRX equivalent at the current rate and both assets are detected automatically.
What happens if the customer sends USDT on another network?
Funds sent on Ethereum or BNB Chain to a TRON address cannot be recovered. The checkout page warns the customer clearly, and you should repeat the warning in your own UI.
How long does the integration take?
Two endpoints and one webhook. Most developers finish in under 30 minutes; the code samples in the docs can be copied as they are.
Accept crypto payments in 30 minutes with KriptoGo
No setup fee, no monthly fee. Just 1% on successful payments.
