---
title: "Accept USDT TRC-20 payments with the API"
description: "Build a USDT checkout on TRON: create an invoice, send the buyer to payment, verify the webhook, and handle partial payments without fulfilling twice."
---

# Accept USDT TRC-20 payments with the API

Use this recipe when your website sells a product or service for a known order
amount and you want customers to pay in USDT on TRON. Your backend creates a
CoinsSend invoice, the buyer follows its checkout URL, and your server confirms
payment before fulfilling the order.

For repeat customer balance top-ups, use
[reusable deposit addresses](/solutions/reusable-deposit-addresses/) instead.

## What you need

- A [CoinsSend merchant account](https://app.coinssend.com/), Merchant ID, and API key.
- A backend that stores orders and payment identifiers. Keep the API key there.
- An HTTPS callback configured in your merchant settings and a
  [webhook receiver](/payments/guides/webhooks/) that verifies signatures.

The complete request contract is in [Invoices](/payments/guides/invoices/).
[PHP](/payments/examples/php-invoice/),
[Node.js](/payments/examples/js-invoice/), and
[Python](/payments/examples/python-invoice/) examples provide the signed request
implementation.

## 1. Choose USDT and the TRON network

Fetch the public catalog before presenting payment choices:

```bash
curl --fail --show-error https://api.coinssend.com/v1/coins-and-fee
curl --fail --show-error https://api.coinssend.com/v1/get-coin-rate
```

Match the coin/network pair in both responses. The API values for USDT TRC-20
are `coin: "usdt"` and `network: "tron"`. `TRC20` is a display label, not the
network code to send. Check current deposit availability; see
[coin selection and operation flags](/payments/supported-coins/).

Display **USDT · TRON (TRC-20)** together. USDT sent over Ethereum is a different
network choice, even though the token has the same name.

## 2. Create one invoice for the order

Send the following body from your backend to `POST /v1/invoices`, with the
`Merchant` and `Sign` headers described in
[Authentication](/payments/guides/authentication/):

```json
{
  "order_id": "shop-order-1042",
  "amount": "25.00",
  "allowed_coins": ["usdt"],
  "coin": "usdt",
  "network": "tron",
  "success_url": "https://shop.example/orders/1042",
  "cancel_url": "https://shop.example/checkout"
}
```

`amount` is the invoice price in **USD**, not a request to collect exactly
25 USDT. The checkout calculates the crypto amount using the applicable rate
and fee settings.

`allowed_coins` restricts coins. `coin` and `network` preselect the payment
option; they are not a documented network allowlist. If your order policy
requires a specific network, reconcile the actual payment pair before
fulfillment.

Store `data.id`, `data.code`, `data.order_id`, and `data.url` against your
local order, then send the buyer to `data.url`. Reuse that checkout link when
the buyer returns to the same order.

The order ID must be unique per merchant. A duplicate returns `409`; it does
not replay the first response. After a timeout, reconcile the first attempt
before creating another invoice.

## 3. Confirm payment on your server

A visit to `success_url` is not proof of payment. Verify the incoming webhook
using `X-Signature` and the **unchanged raw request body**, then handle
`invoice.paid` for the stored invoice and order.

Your fulfillment transaction should:

1. Match the invoice ID and order ID to your stored order.
2. Reconcile the signed status, requested amount, actual paid amount, and payment pair.
3. Mark the local order paid and schedule fulfillment exactly once.
4. Record duplicate deliveries without issuing the product again.

Use a database uniqueness rule or an equivalent atomic transition for the local
fulfillment record. `X-Idempotency-Key` is not guaranteed on every notification;
your invoice/order identifiers must also protect against duplicate fulfillment.

## 4. Show the right state to the buyer

| Situation | Your application's response |
|---|---|
| The buyer returns before confirmation | Show “Awaiting payment confirmation”; keep the order unfulfilled. |
| Invoice lookup reports `partial` | Show the remaining amount from the current checkout state. Partial payment is not a paid order. |
| A verified `invoice.paid` arrives | Fulfill once after reconciling the order and payment. |
| A verified `invoice.expired` arrives | Close the checkout attempt as expired; do not treat any partial amount as a completed order. |
| The same callback arrives again | Acknowledge the already recorded event without repeating fulfillment. |
| The callback or creation result is uncertain | Reconcile stored identifiers and the [invoice lookup](/payments/guides/invoices/#retrieve-invoice); avoid blindly retrying a write. |

CoinsSend does not document a separate webhook for every intermediate invoice
status. Use the current lookup response when your UI needs progress information.

## Continue the integration

- [Invoice fields, fees, and statuses](/payments/guides/invoices/)
- [Webhook verification and delivery behavior](/payments/guides/webhooks/)
- [Choose invoices or reusable addresses](https://coinssend.com/blog/post/static-wallets-vs-invoices-for-crypto-payments/)
- [USDT payment options for your business](https://coinssend.com/accept-usdt-payments/)
- [Open your merchant dashboard](https://app.coinssend.com/)
