USDT payment API: a developer's integration guide

August 28, 2026

USDT payment API: a developer's integration guide

Most guides to accepting USDT through an API start with authentication. That is the wrong first step, because the decision that actually shapes a USDT integration comes before any code: which network you accept it on. Get that wrong and you will support customers who cannot pay you, or absorb network fees that make small payments uneconomic.

This guide covers the network choice first, then the integration. It uses the EukaPay API throughout, where USDT is supported on Ethereum as an ERC-20 token and on Tron as a TRC-20 token, and settlement reaches your bank account in USD, EUR, GBP, CAD.

In this guide, you'll learn:

  • How to choose between USDT on Ethereum and USDT on Tron, and why it matters commercially

  • How to authenticate and create an invoice your customer can pay in USDT

  • How to handle the underpayment and overpayment cases that USDT integrations hit most

  • How to test on Sepolia and Shasta before accepting a real payment

Choosing a network first

USDT is not one asset. It is the same dollar-denominated token issued across several blockchains, and the network determines the fee, the confirmation time and the address format your customer uses.

EukaPay supports USDT on two networks. It is worth being precise rather than quoting a round number of supported currencies:

Network

Token standard

Cryptocurrency ID

Symbol in the docs

Ethereum

ERC-20

3

Tether

Tron

TRC-20

9

Tether (TRON)

There is no USDT on Solana at EukaPay, and no USDC on Tron. If your test plan or your checkout UI assumes either, it will fail for a reason unrelated to your code.

Note that USDT appears twice in that table with the same symbol and two different identifiers, so the symbol alone does not identify a currency. Any internal mapping you build needs the network alongside it.

ERC-20 - the default your enterprise customers expect

USDT on Ethereum is the version most institutional counterparties, treasury desks and audit processes already recognise. If you invoice businesses that hold stablecoins as part of a treasury operation, Ethereum is usually where those balances sit.

The trade-off is network fees. An Ethereum transfer costs the same regardless of whether it moves fifty dollars or fifty thousand, which means a small invoice can carry a fee that is a meaningful percentage of its value.

TRC-20 - the default for high-volume, lower-value payments

USDT on Tron carries substantially lower transfer costs, which is why it dominates in remittance corridors, affiliate payouts and consumer-facing flows where individual amounts are small and volume is high. If your customers are individuals rather than corporate treasuries, they are more likely to already hold USDT on Tron.

How to decide

Ask what your customers already hold rather than which chain you prefer. A checkout that offers only ERC-20 to a customer base holding TRC-20 produces abandoned payments, and the reason will not appear in your logs.

Supporting both is the common answer, and it costs you nothing in code beyond presenting the choice. The network is a property of the payment, not a separate integration.

Authenticating

Generate an API key in the merchant dashboard under Settings > Integrations > API keys. The secret is displayed once at creation, so write it to your secret manager immediately.

Authentication is a single custom header. There is no bearer token, no OAuth flow and no request signing on outbound calls:

curl "https://api.eukapay.com/invoices" \
  -H "x-api-key: sk_live_YOUR_KEY"
  • Hosts.

    Production is

    https://api.eukapay.com

    , staging is

    https://api-stg.eukapay.com

    .

  • No version prefix.

    The path is

    /invoices

    , not

    /v1/invoices

    .

Keys are environment-tagged,

sk_test_

for staging and

sk_live_

for production, so a misconfigured environment is visible by reading the key rather than by debugging a 401.

Creating an invoice a customer can pay in USDT

You do not create a "USDT invoice." You create an invoice denominated in a

fiat

currency, and your customer chooses which supported cryptocurrency to pay it with at the payment screen. The

currencyId

field on

POST /invoices

refers to the fiat currency table, where 1 is the Canadian Dollar, 2 is the US Dollar, 3 is the Euro and 4 is Pound Sterling. Cryptocurrencies live in a separate identifier space, which is the table in the previous section. The two numbering systems overlap, so

currencyId

3 is the Euro and has nothing to do with Tether on Ethereum.

Which cryptocurrencies your customer sees is configured on your account rather than passed per request. So "accepting USDT" is two separate pieces of work: enable the USDT networks you want on your account, then price invoices in fiat as normal.

curl -X POST "https://api.eukapay.com/invoices" \
  -H "x-api-key: sk_live_YOUR_KEY" \
  -H "x-idempotent-key: 5b1e93c7-40a2-4d81-9f36-c7a4e8d21b05" \
  -H "Content-Type: application/json" \
  -d '{
    "price": 480.00,
    "currencyId": 2,
    "number": "INV-260810-17",
    "message": "Pro plan, annual",
    "dueDate": "2026-08-24",
    "redirectUri": "https://example.com/orderCompleted",
    "metadata": { "externalId": "ord_4471", "productCode": "PRO-ANNUAL" }
  }'

The response gives you the invoice

code

and the

paymentUrl

to send the customer to:

{
  "code": "inv_h2qjo7e1qpaled08q0cecn1cal",
  "price": 480,
  "number": "INV-260810-17",
  "status": "Unpaid",
  "paymentUrl": "https://app.eukapay.com/payments/inv_h2qjo7e1qpaled08q0cecn1cal",
  "totalPaidAmount": 0,
  "currency": { "name": "US Dollar", "unit": "USD", "sign": "US$" }
}

Two limits on

price

to design around: the minimum invoice price is 1 and the maximum is 100,000. Once the total reaches 1,000,

customerCode

becomes required, along with

address1

,

city

,

state

and

country

on that customer record, so create the customer with

POST /customers

first for larger invoices.

Always send an idempotency key

The

x-idempotent-key

header is the difference between a robust integration and one that occasionally bills a customer twice. You generate the value, a V4 UUID is recommended, it may be up to 255 characters, and keys expire after 24 hours.

Derive it from your own order identifier rather than generating a fresh value inside your retry logic. A key created on each attempt protects nothing, because every retry looks like a new request. Persist it in the same transaction that marks the order as attempted.

If the API returns an

idempotency_error

, stop and reconcile against

GET /invoices

rather than retrying with a new key. EukaPay documents three error types,

api_error

,

idempotency_error

and

invalid_request_error

.

Reading an invoice back

GET /invoices/{code}

retrieves a single invoice, and

GET /invoices

lists them with cursor pagination using

limit

plus either

starting_after

or

ending_before

. The two cursor parameters are mutually exclusive. Read

has_more

from the

{data, has_more}

envelope to decide whether to keep paging, and set

limit

explicitly rather than relying on the default, which is 100 for invoices but 10 for several other resources.

Handling payment

Configure a webhook endpoint under Settings > Integrations > Webhooks, then drive your order state machine from the events rather than polling.

Verifying the signature

Every delivery carries an

x-eukapay-signature

header containing an HMAC of the payload, keyed to that webhook's secret. The algorithm is

SHA-512

, computed over the raw request body.

const crypto = require('crypto');

function verifyEukaPaySignature(rawBody, signatureHeader, secret) {
  const digest = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
  if (digest.length !== signatureHeader.length) {
    return false;
  }
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signatureHeader));
}

Use

timingSafeEqual

rather than

===

, because a plain string comparison exits on the first differing character and leaks how much of a forged signature was correct.

The three events a USDT integration needs

Event

What your handler does

paymentCompleted

Mark the order paid and fulfil

paymentUnderpaid

Hold the order, request the difference

paymentOverpaid

Fulfil, then handle the surplus

Payment payloads carry

webhookId

,

event

,

paymentCode

,

paidAmount

,

totalPaidAmount

,

status

,

paidDatetime

,

invoiceCode

,

invoiceNumber

,

invoicedAmount

and

invoiceCurrency

. Use

invoiceCode

as the join key back to your own order record.

Underpayment and overpayment - more common with USDT than you expect

These two events deserve real handling rather than a log line, and USDT integrations encounter them more often than card integrations encounter their equivalents.

Two mechanics cause it. A customer sending from an exchange may have the withdrawal fee deducted from the amount they thought they were sending, so the invoice arrives short by exactly the exchange's fee. And a customer typing an amount manually into a wallet rounds it.

Decide the policy before you write the handler:

  • Underpaid.

    Compare

    totalPaidAmount

    against

    invoicedAmount

    , hold fulfilment, and tell the customer the remaining balance.

    totalPaidAmount

    matters because a customer may top up with a second transfer.

  • Overpaid.

    Fulfil the order, then route the surplus through your refund process using

    POST /refunds

    . Do not reject the payment because an equality check failed.

Handling retries

EukaPay retries a failed delivery every 20 minutes for up to two hours, so your handler will receive duplicates. Record the

webhookId

and return early on a repeat. Verify the signature, deduplicate, return a 2xx quickly, and do slow work asynchronously.

Settlement

When a customer pays in USDT, EukaPay applies instant crypto-to-fiat conversion at a locked exchange rate to remove all crypto volatility. Your business recognises a fiat amount rather than holding a token balance.

Settlement reaches your bank account in USD, EUR, GBP, CAD, and Canadian merchants can also take Interac withdrawals. For a business accepting USDT specifically, the locked rate is the part worth understanding: USDT tracks the dollar closely, but the rate at which it converts into your settlement currency is still a rate, and locking it at the moment of payment is what removes the exposure between payment and settlement.

Going live

Test both networks before you accept a real payment. Each has its own test network, and using the wrong one produces a transaction that confirms on-chain and never appears in your account.

What you are testing

Test network

USDT on Ethereum

Sepolia

USDT on Tron

Shasta

Register for staging at

stg.eukapay.com

and call

https://api-stg.eukapay.com

with a

sk_test_

key. Staging verification emails are not sent automatically, so request approval from support. Test USDT is provided by EukaPay support rather than a public faucet, so ask for it in the same message.

A short pre-launch list:

  • Run the full loop on both Sepolia and Shasta, not just one

  • Rehearse underpayment and overpayment deliberately

  • Replay a captured webhook payload and confirm your handler deduplicates by

    webhookId
  • Alter one byte of a payload and confirm signature verification rejects it

  • Confirm idempotency keys are persisted before the request is sent

  • Move the host and key to configuration, then send one small real payment and watch it through to settlement

Every EukaPay merchant goes through an onboarding and business review process, and there is no instant registration. The sandbox exists so the integration work happens during that review rather than after it.

One platform underneath

USDT is one currency on a platform that treats all of them the same way: instant crypto-to-fiat conversion at a locked exchange rate to remove all crypto volatility, protection against chargebacks, support for a wide range of cryptocurrencies, and settlement in USD, EUR, GBP, CAD to your bank account. EukaPay supports most major cryptocurrencies like BTC, ETH, LTC, SOL, USDC, USDT.

Adding USDC on Ethereum, or Bitcoin, or Solana later is a currency configuration rather than a second integration. The endpoints, the webhook events and the signature verification are identical.

Get started with EukaPay

Create an account at

app.eukapay.com/signup

, complete verification, provide your legal business information, then generate an API key.

The full reference, including request schemas, the currency table with network coverage, and every error type, is public at

docs.eukapay.com

. There is also an official documentation MCP server at

docs.eukapay.com/mcp

if a coding agent is doing the integration. For the merchant-facing view rather than the developer detail, see

how to accept USDT payments

.

Frequently asked questions

Which USDT networks does EukaPay support?

Ethereum as ERC-20 and Tron as TRC-20. There is no USDT on Solana.

Should I accept USDT on Ethereum or Tron?

Accept both where you can. Tron carries lower transfer fees and suits high-volume, lower-value payments, while Ethereum is what most corporate treasuries already hold.

How do I stop a retry from creating two invoices for one order?

Send an

x-idempotent-key

header derived from your own order identifier, and persist it before sending the request. Keys expire after 24 hours.

What signature algorithm does EukaPay use for webhooks?

HMAC SHA-512, in the

x-eukapay-signature

header, computed over the raw request body.

What happens when a customer underpays a USDT invoice?

You receive a

paymentUnderpaid

event carrying

paidAmount

,

totalPaidAmount

and

invoicedAmount

, so your code can hold the order and request the balance. This commonly happens when an exchange deducts its withdrawal fee from the amount sent.

Where do I get test USDT for the sandbox?

From EukaPay support. Test stablecoins are not available from a public faucet, unlike the base network coins.

What currency does EukaPay settle USDT payments in?

Your chosen fiat, settled to your bank account in USD, EUR, GBP, CAD, with Interac withdrawals available for Canadian merchants.

Does EukaPay publish a USDT SDK?

The API is a standard REST interface, so most teams call it directly with

fetch

,

axios

, or their language's HTTP client - no SDK to install or keep up to date.