Accept only the coins you want: limiting a crypto checkout with allowed_assets
By default an easyway invoice priced in dollars lets the customer pay with any of the 58 coins on 68 coin/network pairs. That is the right default for most shops, but not for all of them. Some businesses keep their books in stablecoins only, some don't want to hold a volatile token even for an hour, and some have learned that every extra network is another way for a customer to send money to the wrong place. One field on the invoice handles all of these: allowed_assets.
Three ways to set the coins on an invoice
| You send | The customer sees | Typical use |
|---|---|---|
currency: "USD", nothing else | Every coin and network that is enabled | A general shop that takes whatever the customer holds |
currency: "USD" + allowed_assets | Only the ids you listed, grouped by coin | Stablecoins only, or only cheap networks |
currency: "USDT_TRC20" (an asset id) | Only that coin; the amount is in that coin | Topping up a balance kept in one coin, or a price agreed in crypto |
Each entry of allowed_assets is an asset id: a coin and its network, such as USDT_TRC20 or USDC_SOL. USDT on TRON and USDT on Ethereum are different ids, because they arrive on different chains and are withdrawn separately. The full list is in the Coins & networks section of the docs.
Example: USDT only, on three networks
A common setup is "USDT, on the networks our customers actually use", for example TRON, BNB Smart Chain and TON, which most exchanges offer for USDT withdrawals. Each network you keep is a separate USDT balance with its own withdrawal fee (on easyway USDT on Ethereum costs 1.5 USDT to withdraw, on BNB Smart Chain 0.1 USDT), so a shorter list also means fewer balances to manage. The invoice is still priced in USD; the customer picks one of the three networks and the checkout locks the exact USDT amount for that choice.
// Node 18+ (built-in fetch). Run: EASYWAY_API_KEY=sk_test_... node usdt-only.mjs
const res = await fetch("https://api.easyway.cash/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EASYWAY_API_KEY}`,
"Idempotency-Key": "order-3107",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "49.00",
currency: "USD",
order_id: "3107",
// Only USDT, and only on networks where sending it costs the customer little.
allowed_assets: ["USDT_TRC20","USDT_BEP20","USDT_TON"],
}),
});
if (!res.ok) throw new Error(`easyway ${res.status}: ${(await res.json()).error}`);
const invoice = await res.json();
console.log(invoice.id, invoice.payment_url);The checkout filters its coin list to the ids you sent and keeps grouping them by coin, so the customer sees one USDT entry with three networks to pick from. The quick-pick shortcuts at the top of the list only show the ones that are in your list.
Stablecoins only
To accept any dollar stablecoin but nothing volatile, list the stablecoin ids. These are the ones the gateway marks as stable today, read from its own asset list:
| Asset id | Coin | Network |
|---|---|---|
USDT_TRC20 | USDT | TRON · TRC-20 |
USDT_BEP20 | USDT | BNB Smart Chain · BEP-20 |
USDT_TON | USDT | TON · Jetton |
USDT_ERC20 | USDT | Ethereum · ERC-20 |
USDC_ERC20 | USDC | Ethereum · ERC-20 |
USDC_BEP20 | USDC | BNB Smart Chain · BEP-20 |
USDT_SOL | USDT | Solana · SPL |
USDC_SOL | USDC | Solana · SPL |
USDT_POLYGON | USDT | Polygon · PoS |
USDC_POLYGON | USDC | Polygon · PoS |
USDT_AVAX | USDT | Avalanche · C-Chain |
USDC_AVAX | USDC | Avalanche · C-Chain |
DAI_ERC20 | DAI | Ethereum · ERC-20 |
FDUSD_BEP20 | FDUSD | BNB Smart Chain · BEP-20 |
PYUSD_ERC20 | PYUSD | Ethereum · ERC-20 |
USDS_ERC20 | USDS | Ethereum · ERC-20 |
You don't have to take all 16. Fewer options mean a shorter list and fewer questions from customers who are unsure which network their wallet uses. The USDT networks comparison helps decide which ones to keep.
One coin: the address comes back in the response
When only one coin can be paid, either because allowed_assets has a single id or because currency is an asset id, there is nothing for the customer to choose. The gateway selects that coin while it creates the invoice, so the 201 response already carries pay_asset, pay_amount and pay_address (plus pay_memo on TON and XRP, which the customer must include).
curl -X POST https://api.easyway.cash/v1/invoices \
-H "Authorization: Bearer $EASYWAY_API_KEY" \
-H "Idempotency-Key: order-3108" \
-H "Content-Type: application/json" \
-d '{"amount":"49.00","currency":"USD","order_id":"3108","allowed_assets":["USDT_TRC20"]}'That makes a fully custom payment screen possible: show the address and amount in your own app and keep payment_url as a fallback. For a USD-priced invoice the coin amount is quoted at that moment and stays locked for the invoice's lifetime, which you can set with lifetime (5 to 1440 minutes).
What goes wrong, and the error you get
| Mistake | Result |
|---|---|
An id that doesn't exist, e.g. "USDT" without a network | 400 validation_error, with allowed_assets named in fields |
| Choosing a coin outside the list on the checkout | 400 asset_not_allowed; the page never offers it in the first place |
currency set to a coin that is not in allowed_assets | 400 asset_not_allowed when you create the invoice |
Nothing about the fee changes: easyway takes a flat 0.5% of each confirmed payment whichever coin it arrives in, and the payment is credited to that coin's balance. Test the list with an sk_test_ key first; the sandbox guide shows how to simulate the payment and the webhook.
Selling without code? A payment link offers every enabled coin; the coin list can be narrowed only on invoices created through the API.