Skip to main content
This page covers stablecoin acceptance through the normal wallet / hosted-cashier path. The AI-Agent path built on the x402 protocol has its own scope, request fields and error behaviour, and is documented in x402 stablecoin acquiring. Exception cases and refunds are in Stablecoin Exceptions & Refunds. Acceptance is enabled per merchant contract — ask the integration team to provision the CRYPTO payment product and your chain set before you build.

How many ways are there to accept stablecoins, and which do I pick?

Two, and they are not variants of one integration — they differ in who the payer is and in what your server does: Pick on the payer, not on preference: a human with a wallet cannot pay an x402 challenge, and an Agent has no browser to render a checkout in. If you sell to both, you integrate both.

What do I send on order creation to accept stablecoins?

Three things identify a stablecoin order on the wallet path: The method is catalogued under the global region code GLB rather than a single country, so it is not filtered by the shopper’s country. Pricing works like any other cross-currency order: keep orderCurrency and orderAmount in your pricing currency (typically USD), and set userCurrency to USDC or USDT — the currency the shopper actually pays in. See Pricing currency strategy.

Do I have to make the shopper pick a chain myself?

Only if you run your own checkout page.
  • Waffo-hosted cashier — omit payMethodProperties.cryptoChain. The consultation step returns it as a missing field together with the chain options available for the order, and the cashier collects the shopper’s choice before the order is finalized.
  • Your own checkout page — present the chain choice yourself and send the selected value back in payMethodProperties.cryptoChain.
Treat that returned option list as authoritative: it reflects what is available for your account and that stablecoin, which can be narrower than the platform-wide list below.

Can I offer only one chain, so the shopper cannot pick the wrong one?

Yes — that is what the option list is for. The chains offered for an order come from your account’s configuration, so a single-chain configuration produces a single-chain option list and, on the hosted cashier, no chain selector worth speaking of. Ask your integration contact for the chain set you want rather than filtering the list client-side: a client-side filter still leaves the wider set acceptable on order/create. Note what this does not prevent. Narrowing the chains removes the choice of which order the shopper pays; it cannot stop them sending funds on some other chain from their wallet, which is the WRONG_CHAIN case in Stablecoin Exceptions & Refunds.

What exactly do I put in the chain field?

The chain’s code, as a string — BASE, not Base, and not a numeric chain ID. Matching is case-insensitive, and each chain’s short name is accepted as an alternative to its code, but send the code: it is the value that comes back on the order and the value the refund path reads. Testnets are separate codes, usable where your sandbox is configured for them: Tron and BNB Smart Chain have no testnet code, so those two cannot be exercised against a testnet. Two naming points that come up often:
  • BSC is BNB Smart Chain. The chain’s code is BSC and its network is BNB Smart Chain; “BNB” on its own is the network’s token, not a chain code. Send BSC.
  • Avalanche means the C-Chain. Only C-Chain (EVM) addresses are valid; X-Chain and P-Chain addresses are rejected outright, including on refunds.
The address family column is the one to build against: it determines the address format the chain accepts, which matters most on refunds — see Stablecoin Exceptions & Refunds.

Is the chain field mandatory? What happens if I leave it out?

It is not mandatory on the request, but the order cannot be paid without a chain, so leaving it out has one of two outcomes and never a default:
  • On the hosted cashier, consultation reports it as a missing field with the available options, and the shopper’s choice supplies it.
  • On your own checkout, you get the same missing-field response and it is your job to collect the choice and re-send.
There is no fallback chain. Do not assume the cheapest or the most popular chain is picked for you.

Which stablecoins are supported?

USDC and USDT on the wallet path; USDC only on the x402 path. No other tokens — no non-stablecoin tokens, no small-cap tokens, and no merchant-issued tokens. Anything else sent to a receiving address is not a valid payment: a transfer of the chain’s native token (ETH, for example) or of any other token does not pay the order, and neither does a transfer on a chain other than the order’s.

Is there a minimum or maximum amount?

Yes, and it is on your agreement like any other payment method, not a special stablecoin rule. The published payment method catalogue carries the catalogue-level range for USDC and USDT; the range that actually applies to you is orderAmountMin / orderAmountMax on your own configuration entry for the method, readable from /api/v1/paymethodconfig/inquiry or from Service → Payin in the Portal. Check it before you create the order. An amount outside the range is rejected at order creation with A0008 — see Limits, Currency & Configuration Errors for how that differs from the two account-level limits.

How many decimal places do stablecoin amounts use?

Two, on every Waffo API field, exactly like a 2-decimal fiat currency — see Currency and amount. On-chain the tokens use 6-decimal atomic units; Waffo handles that conversion, so neither your requests nor your reports carry atomic units, and a block explorer shows the amount in a different unit than the one you sent. The token’s on-chain precision therefore does not change what you send: the same orderAmount string is correct on every chain, and you never scale it yourself. The one place atomic units are yours to construct is the x402 signature path, where the challenge and the signature are both in atomic units — that page spells out the conversion.

Is the receiving address mine, per order, or fixed?

It depends on the path, and the two answers are genuinely different: On the wallet path an address is bound to one order and released when that order finishes, which means it can later be issued for a different order. That is the reason the rule is absolute rather than a preference: a transfer to an address you cached does not pay the order you cached it for, and once the address has moved on it cannot be attributed to it at all. Neither path gives you an address of your own to publish, print on an invoice, or hard-code. In both, the address is Waffo’s; Waffo receives on-chain and settles to you per your contract.

The shopper paid to a recycled address. What happens?

Nothing good, and this is worth designing against rather than handling. Once an order’s address has been released, a late transfer to it lands in one of the unmatched cases in Stablecoin Exceptions & Refunds: it does not pay the original order, and it does not pay whichever order holds the address now, because the amount and the order will not correspond. Practical guards: never render a stablecoin address outside the payment page for that order, never let a shopper “reuse the address from last time”, and never show a QR code from an expired order.

Does the payment QR code carry the amount?

Do not assume it does. The reliable instruction to a shopper is: send exactly the order amount, in the order’s token, on the order’s chain, before the window closes — all four, from the payment page. A wallet that pre-fills the amount is a convenience, not a guarantee, and an amount the shopper edits produces an overpay or underpay case rather than a paid order.

How long does the shopper have to pay?

The order’s expiry, which is the same mechanism as every other payment method: the contract default unless you send orderExpiredAt on order/create. See Payment Timeouts & Finality. Two stablecoin-specific things to plan for:
  • The window has to cover the shopper’s action plus the chain’s confirmation time, not just the shopper’s action. A window that would be generous for a wallet redirect can be tight on a chain that confirms slowly.
  • A transfer that arrives after the window closes does not pay the order. It becomes the LATE_PAID case, and there is no way to attach it to the closed order afterwards.

The shopper’s wallet says the transfer succeeded — why is the order still processing?

Because the order advances to success only once the transfer meets the confirmation requirement on-chain, and that wait is a property of the chain, not of Waffo. A wallet showing “sent” is not the same event as the payment being final. How long the wait is varies substantially between chains:
  • Chains with near-instant deterministic finality (for example Avalanche, Polygon) confirm quickest.
  • Ethereum mainnet waits for a block-confirmation depth, so it is noticeably slower.
  • Optimistic-rollup L2s (Arbitrum, Base, Optimism) are slowest: the sequencer accepts the transfer in seconds, but confirmation waits for the batch to be confirmed on Ethereum.
So show the shopper a “waiting for network confirmation” state, and treat the payment-result webhook or an order inquiry as the only source of truth for success — never the wallet’s own confirmation screen, and never a block explorer you checked yourself.

The order has been processing for a while. Is it still waiting, or has it failed?

Read the order, not the chain. An order in a non-final state is still in play; the state machine is the same as for every other payment method, so the same rules apply about which states are final and which are not — see Payment lifecycle and Payment Timeouts & Finality. What you must not do is infer failure from the chain being slow, or success from having found the transfer on a block explorer. If the order has not reached a final state by its expiry, it closes; if funds arrived off-spec, it becomes one of the exception cases, which are visible to support with the order id and the transaction hash.

Do I receive stablecoins or fiat?

Fiat. The USDC or USDT the shopper pays is converted at payment success through the USD/USDC and USD/USDT currency pairs, and everything downstream — your unsettled balance, settlement batches and reports, and withdrawals — stays in your settlement currency. You never hold the stablecoin, so you carry no token price exposure and no on-chain fee exposure. Because conversion happens at payment success, not at order creation, the rate is the rate at that moment. Stablecoin orders use the same cross-currency machinery as any other cross-currency order, so the rate mechanics, and whether a quote can be held, are covered in Exchange Rates and Pricing currency strategy rather than being special here.

Where do stablecoin orders show up in the Portal?

In the ordinary transaction list, filtered like anything else. Two filters get you there: What the Portal does not give you is any on-chain detail: there is no chain column, no chain filter, no transaction hash, no confirmation count and no network-fee figure on the transaction list or the transaction detail. The one place a chain appears is the refund drawer, where it is shown read-only as the chain the original payment used. So if your reconciliation needs the chain or the transaction hash per order, do not plan to get it from a Portal export. On the x402 path the hash is available on the API — the settlement receipt returned on success carries it, and that page documents its shape. On the wallet path, raise the order id with support.

What do I need in order to be enabled for stablecoins?

Two provisioning steps, both arranged with your integration contact: the CRYPTO payment product on your contract, and your chain set. Until both are in place, stablecoin order creation is refused as not permitted by your contract (A0010). Enrollment itself follows the normal path, including where you watch its status — see Payment Method Enrollment. Adding a chain later is the same kind of request as adding a payment method: it is a configuration change on the Waffo side, not something you can switch on yourself.

Where do I look next?