Let the customer pick the network
USDC is issued on several chains, and halfin accepts it on the three that matter for payments — Ethereum, Base and Solana. The mistake is to hard-code one. A customer who holds USDC on Base cannot pay you on a network you happened to pin, and forcing a single rail is how a ready-to-pay customer becomes an abandoned checkout.
The way around that is to leave the asset open when you create the invoice. You bill in fiat; halfin presents the supported USDC networks at the point of payment and lets the payer settle from wherever they already hold the token. One invoice covers all three rails, and every one of them credits to the same dollar-denominated balance, so you reconcile once rather than three times.
The three rails are not interchangeable from the payer's side, and the differences are mostly about cost. Ethereum (ERC-20) is USDC's canonical home and the path institutional and EVM-native wallets default to, but it carries the highest network fee of the three. Base is an Ethereum L2 built for low-cost settlement: the same ERC-20 USDC moves for a fraction of mainnet cost, which makes Base the sensible default for everyday and smaller-ticket dollar payments. Solana settles USDC quickly and cheaply as an SPL token for payers already in that ecosystem. The dollar value is identical across all three — only the rail differs.
Pick the network by the job, not the brand
All three rails carry the same dollar token, but they don't behave the same at checkout. The table is the real supported surface for USDC on halfin — these three and no others — read by what each one is good for, so you steer rather than enforce. For small tickets the network fee, not your price, is often what decides whether a customer completes the payment, which is the practical case for nudging everyday USDC onto Base.
| Network | Token standard | Best for | Settlement character |
|---|---|---|---|
| Base | ERC-20 (Base L2) | Everyday and smaller-ticket dollar payments; low-cost settlement default. | Reorg-aware credit at Base's confirmation threshold. |
| Ethereum | ERC-20 (mainnet) | Larger payments and institutional / EVM-native wallets holding USDC on mainnet. | Reorg-aware credit at Ethereum's confirmation threshold. |
| Solana | SPL | Payers already in the Solana ecosystem wanting fast, low-fee settlement. | Reorg-aware credit at Solana's confirmation threshold. |
Bill in dollars, get paid in USDC
USDC is a dollar-denominated stablecoin: one unit is intended to track one US dollar. "Tracks" is not "is", though — the live USDC/USD rate sits very near one and moves in small amounts. halfin keeps you out of that. You create an invoice with a fiat amount and currency; halfin maps that figure to the payable USDC amount and locks it when the invoice activates. The customer sees a concrete number to send; your ledger records the dollars you billed.
Because the quote is pinned at activation rather than at draft time, the amount the customer is shown does not drift while they open their wallet and confirm. Each invoice carries an expiry — if the payment window lapses, the invoice expires instead of silently re-pricing against a newer rate, and you decide whether to re-issue. If a customer underpays, for instance because an exchange skims a withdrawal fee on the way out, the shortfall is recorded against the invoice rather than stranded; an overpayment is recorded the same way, so the excess stays visible and accountable.
The net effect is that USDC behaves, in your books, like the dollars you priced in. Because the asset is already dollar-denominated, the gap between what you billed and what settles is small to begin with, and the fiat anchor closes it entirely — you reconcile against the USD figure, not whatever the token traded at the second the payment confirmed.
- Bill a USD (or other fiat) amount — you never quote a USDC figure by hand.
- halfin locks the rate at activation, so the payable amount doesn't drift while the customer pays.
- Underpaid and overpaid amounts are recorded against the invoice, not lost.
- A lapsed invoice expires rather than re-pricing against a newer rate.
Accept USDC with one API call
Accepting USDC is not a USDC-specific integration. It is the standard invoicing flow: you post a fiat amount and currency to the public REST API with a scoped API key, and halfin returns a payable invoice you present through hosted checkout or render yourself. Leaving the asset unpinned is what lets the customer choose USDC on Ethereum, Base or Solana at the point of payment.
The call below is minimal. The amount is a string — monetary values are strings end to end, never floats — and the currency is your fiat anchor. The idempotency_key field in the request body makes a retried request safe: the same key never creates a second invoice. The full request and response schema lives in the docs and the @halfin/sdk-merchant types; the point here is that one authenticated call produces an invoice a customer can settle in USDC on any of the three networks.
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": "49.00",
"fiat_currency": "USD",
"deferred": true,
"description": "Order 5108 — USDC accepted",
"idempotency_key": "00000000-0000-4000-8000-000000000003"
}'
# No asset pinned, so the payer chooses USDC on Ethereum (ERC-20),
# Base or Solana at the point of payment. halfin locks the rate at
# activation and pins the payable USDC amount. Track state via signed
# webhook events and verify the HMAC signature before acting. Full
# response schema at docs.thehalfin.com.Know a USDC payment is final
Accepting USDC is only useful if you know when the money has actually arrived. You learn that from webhooks, not by polling a block explorer. halfin emits an HMAC-signed event for each step of the invoice lifecycle, and your endpoint reacts to them. Verify the signature before you take any business action — releasing goods, marking an order paid, sending a receipt — because an unsigned or mis-signed payload is not something to act on.
The events you care about for an inbound USDC payment are the ordinary invoice lifecycle. invoice.confirming fires once a payment is seen but not yet final; invoice.paid fires when the amount has held to the network's confirmation threshold and the dollar figure is settled. invoice.underpaid and invoice.overpaid tell you the amount didn't match exactly, so you can decide how to handle the difference, and invoice.expired tells you the window lapsed unpaid. Confirmation thresholds are set per chain because finality means different things on Ethereum, on the Base L2, and on Solana — and crediting is reorg-aware throughout, so invoice.paid means the figure actually stuck on the chain the customer chose.
- invoice.confirming — a USDC payment is seen, not yet final.
- invoice.paid — settled to the dollar figure at the network's confirmation threshold.
- invoice.underpaid / invoice.overpaid — the amount didn't match; you decide how to reconcile.
- invoice.expired — the payment window lapsed unpaid.
- Always verify the HMAC signature before acting on any event.
After the payment: balance, payouts, refunds
Accepting USDC is the front door; the balance it produces is the rest of the platform. Paid USDC invoices accrue to a balance you can hold, convert or disburse. If you owe a stablecoin to a partner, affiliate or supplier, single and mass payouts send USDC back out on a supported network. If a customer is owed money back, refunds handle the return path on a supported rail.
A USDC balance on one network doesn't lock you into that network — balance conversion lets you rebalance into another asset or rail as your treasury needs. And because the whole flow is fiat-anchored, the USDC you accept sits in the same ledger as USDT and the native chain assets, settled to the same dollar figures, rather than as separate pots you reconcile by hand. Many merchants offer both stablecoins on the same invoice — USDC for US and EVM-native payers, USDT for the regions and exchanges where it dominates — without a second integration.