Guide

How to build a multi-chain crypto payment strategy

Your customers do not all hold the same coin on the same chain. One pays TRC-20 USDT because the network fee is trivial; another only has BTC in cold storage; a third lives on Solana and expects fast finality. A single-chain integration quietly turns those people away at checkout. A multi-chain strategy is the opposite stance: present the rails your payers actually use, anchor every invoice to one fiat figure regardless of how they settle, and reconcile all of it through one model. This guide walks the strategy end to end — decide which chains to accept, map assets to networks, present several rails on one invoice, choose defaults by cost and payer habit, and keep settlement and reconciliation identical across all of them.

01

Why one chain is a checkout leak

Pinning your acceptance to a single chain is an invisible conversion tax. If you only take USDT on Ethereum, the customer holding TRC-20 USDT has to bridge or swap before they can pay you — and most will not bother. If you only take BTC, the stablecoin-native payer who never touches volatile assets has nowhere to land. Every chain you do not accept is a segment of payers who arrive ready to pay and leave because the rail they hold is not on the page.

Multi-chain acceptance closes that leak without multiplying your accounting. The trick is the fiat anchor: a halfin invoice can carry a fixed fiat amount (you state the amount in USD or EUR with deferred pricing), and the customer settles the equivalent in whichever supported asset and network they hold. The number you booked never changes — only the rail the customer used to satisfy it changes. So 'accept more chains' does not mean 'maintain more price books'; it means one invoice draws from a wider menu.

The cost of breadth is operational, not financial-modelling: more chains means more confirmation behaviours, more address formats, and more network-fee characteristics to understand. halfin absorbs most of that — crediting is reorg-aware and each chain has its own confirmation threshold — but your strategy still needs to decide which rails are worth presenting and which default to lead with. That decision is the rest of this guide.

  • Every unsupported chain is a payer segment that arrives ready and leaves unable to pay.
  • A fiat-anchored invoice keeps one booked figure no matter which rail settles it.
  • Breadth costs operational understanding (confirmations, fees, formats), not extra price books.
  • Strategy = decide which rails to present and which to lead with, not how to re-model money per chain.
02

Step 1 — Decide which chains to accept

Start from your payers, not from a list of chains you find interesting. The chains worth accepting are the ones a meaningful slice of your customers already hold value on. halfin runs real gates for a fixed set of chains — accept from within that set, and choose based on who pays you. The table below is the actual supported surface; there is no chain on it you cannot use, and no chain off it you can.

Read the table as a decision aid, not a checklist to fully implement on day one. A stablecoin-heavy customer base argues for Tron, Ethereum, and Solana first, because that is where USDT and USDC liquidity sits. A customer base that holds native crypto argues for BTC and ETH. A latency-sensitive flow — top-ups, in-app credit — argues for the fast-finality rails. You can present all of them; you should lead with the ones your payers actually reach for.

Whatever you accept, you accept the same way. There is no per-chain integration to build: the chains below are settlement options on one invoice and payout API, so adding a rail to your strategy is a content and defaults decision, not a new code path.

ChainAssets you can acceptWho reaches for it
Bitcoin (BTC)BTCHolders paying from cold storage or BTC-only wallets.
Ethereum (ETH + ERC-20)ETH, USDT (ERC-20), USDC (ERC-20)EVM-native payers; high-value settlement where fee matters less.
Base / Arbitrum / PolygonNative chain asset, USDC (Base)EVM payers wanting Ethereum-style assets with lower network fees.
BNB Smart Chain (BSC)Native chain assetPayers already holding value on BSC.
Tron (TRC-20)USDT (TRC-20)Stablecoin payers optimising for low network fees.
Solana (SOL + SPL)SOL, USDT, USDCPayers expecting fast finality and low fees.
XRP Ledger (XRP)XRPPayers wanting fast finality on the XRP Ledger.
03

Step 2 — Map assets to networks deliberately

The most common multi-chain mistake is treating an asset as if it lived on one chain. The same ticker settles on several networks, and a destination that can receive USDT on Tron cannot necessarily receive USDT on Ethereum. Asset and network are two fields, not one — and getting the pairing wrong is how a payment lands somewhere unrecoverable.

Stablecoins are where this matters most, because they are the rails most of your payers will use. USDT runs on Tron, Ethereum, and Solana. USDC runs on Ethereum, Base, and Solana. A payer who holds TRC-20 USDT and a payer who holds ERC-20 USDT both 'have USDT', but they are on different rails with different fee profiles, and an invoice has to present the specific pairing each one can satisfy. The asset-to-network matrix below is the real surface to design against — present pairings from it, never an aspirational superset.

Native assets are simpler — BTC is Bitcoin, SOL is Solana, XRP is the XRP Ledger — but the same discipline applies: the network is part of the payment identity, and your presentation and reconciliation both key on the asset-plus-network pair, not on the ticker alone.

AssetNetworks halfin gatesStrategy note
USDTTron (TRC-20), Ethereum (ERC-20), SolanaLead with Tron for low-fee stablecoin payers; offer ERC-20 / Solana for payers already there.
USDCEthereum (ERC-20), Base, SolanaBase and Solana give USDC payers lower network fees than Ethereum mainnet.
BTCBitcoinOne rail; expect slower confirmation than the stablecoin chains.
ETHEthereumEVM-native settlement and ERC-20 token payers.
SOLSolanaFast finality, low fee — good default for latency-sensitive flows.
XRPXRP LedgerFast finality on its own ledger.
04

Step 3 — Present several rails from one fiat-anchored invoice

Here is the payoff of the strategy: you do not build a separate flow per chain. You create one invoice anchored to a fiat amount, and the customer chooses the rail they hold at checkout. Because the invoice is fiat-anchored, halfin computes the payable token amount on whichever asset and network the customer picks, and the fiat figure you booked stays fixed. The rate locks when the invoice activates and the invoice carries an expiry, so the customer pays a concrete, agreed amount inside a bounded window regardless of which chain they settle on.

The create call below is the whole integration for breadth. You post the fiat amount and currency with deferred pricing; you do not enumerate chains in the request, because acceptance is a property of your account's enabled gates, not of the individual invoice body. Send the amount as a string — monetary values are strings end to end, never floats — and pass an idempotency_key in the request body so a retried create never produces a duplicate invoice. Present the returned invoice through hosted checkout, where the customer sees the supported rails, or render the choice yourself with self-hosted checkout.

Once the invoice is live, the customer's chain choice is their concern, not yours. They pick Tron USDT or Base USDC or BTC; halfin shows the address, the exact payable amount for that rail, the network, and the countdown. You wait for the same paid signal regardless of which one they used.

# One fiat-anchored invoice the customer can settle on any enabled chain.
# You do NOT list chains in the body — acceptance is your account's gates.
# idempotency_key is a snake_case BODY field, never an Idempotency-Key header.
curl -X POST https://api.thehalfin.com/api/v1/invoices \
  -H "X-API-Key: $HALFIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_fiat": "120.00",
    "fiat_currency": "USD",
    "deferred": true,
    "description": "Order 7782 — multi-chain checkout",
    "redirect_url": "https://shop.example.com/thanks?order=7782",
    "idempotency_key": "order-7782"
  }'

# The customer picks a rail at checkout (Tron USDT, Base USDC, BTC, ...).
# halfin locks the rate at activation, pins the payable token amount for the
# chosen network, and you track the result via signed webhooks.
# See docs.thehalfin.com for the full request and response schema.
05

Step 4 — Choose defaults by cost and payer habit

Presenting many rails is necessary but not sufficient — what you lead with shapes which rail most customers actually use, and that has real consequences for them. Network fees differ qualitatively across chains: moving value on some rails costs the payer almost nothing, while the same transfer on a congested mainnet can cost meaningfully more. You cannot quote those fees (they are a property of the chain and the moment, not of halfin), but you can steer toward the cheaper, faster rail for the typical payer by ordering and defaulting your options well.

Match the default to the payment's shape. For a small stablecoin payment — a subscription, a top-up — leading with a low-fee stablecoin rail keeps the network fee from being a noticeable fraction of the payment. For a large settlement, the network fee is a rounding error and the payer's existing holdings matter more, so lead with whatever asset they are likely to hold at that size. For a latency-sensitive flow where the customer is waiting on a screen, a fast-finality rail reduces the time they spend staring at a confirming state.

Let payer habit override theory. The 'best' rail on paper is the one your customer does not hold; the best rail in practice is the one they can pay from in two taps. If your analytics show most payers settling in TRC-20 USDT, lead with it even if another chain is marginally cheaper — a rail a payer already holds beats a slightly cheaper rail they would have to acquire. Treat your defaults as a hypothesis you revise from what payers actually choose.

Payment shapeLead rail to default toWhy
Small recurring / top-upLow-fee stablecoin rail (e.g. TRC-20 USDT, Solana / Base USDC)Keeps the network fee from being a visible slice of a small amount.
Large one-off settlementWhatever the payer holds at size (often ETH / ERC-20)Network fee is negligible relative to the amount; holdings dominate.
Latency-sensitive (waiting on screen)Fast-finality rail (Solana, XRP)Shortens the time the customer spends in a confirming state.
BTC-holding customer baseBTCMeet payers where their value already sits, even if confirmation is slower.
06

Step 5 — Keep settlement and reconciliation identical across rails

The strategy only pays off if breadth at the front does not become chaos at the back. It does not, because every rail terminates in the same model. Crediting is reorg-aware and each chain waits for its own confirmation threshold, but the signal you act on is the same across all of them: a signed webhook telling your backend the invoice reached a terminal state. You do not write a per-chain settlement handler; you write one handler keyed on the invoice event.

Drive your fulfilment from invoice events, not from watching chains. invoice.confirming tells you a payment is in flight under the chain's threshold; invoice.paid tells you the full amount settled; invoice.underpaid and invoice.overpaid surface the awkward middle cases; invoice.expired tells you the window closed unpaid. For each, verify the HMAC signature over the raw request bytes with a constant-time compare before you act — an unsigned or mismatched request is not a halfin event and must never move your state. Then fulfil idempotently, because a webhook can be redelivered.

Reconciliation is where the fiat anchor earns its keep. Because every invoice maps back to the fiat amount you booked — matched by the asset and network it actually arrived on — a multi-chain month reconciles exactly like a single-chain one: confirmed invoices against booked figures, with the rail recorded for each. The customer's choice of chain is a fact you store, not a branch in your accounting. One model in, one model out, however many rails you presented in the middle.

  • One webhook-driven handler covers every chain — no per-rail settlement code.
  • Verify the HMAC over raw bytes (constant-time) before acting on any event.
  • Drive fulfilment from invoice.* events; dedupe so a redelivery fulfils once.
  • The fiat anchor makes a multi-chain month reconcile like a single-chain one.