What you can accept on Ethereum
On Ethereum mainnet, halfin handles two distinct things through one settlement path: native ETH, the chain's gas asset, and ERC-20 tokens, which are smart-contract tokens that ride on top of it. The split matters because they move differently on-chain — native ETH is a balance transfer, an ERC-20 transfer is a contract call — but for you the experience is the same: the customer pays the asset they hold, and you reconcile the fiat amount you billed.
The two ERC-20 tokens that carry almost all stablecoin volume are USDT and USDC. USDT on Ethereum is the ERC-20 issuance of the same dollar-denominated stablecoin a customer might otherwise send as Tron TRC-20. USDC on Ethereum is a dollar-denominated stablecoin available on Ethereum, Base, and Solana. A customer paying you in dollars-on-chain will most often reach for one of these, and on Ethereum both are ERC-20 transfers settling to the address halfin presents on the invoice.
Native ETH is the right asset for an EVM-native payer who holds ETH directly — someone settling out of a hot wallet, a DeFi position, or an exchange withdrawal. Because ETH is the gas asset, a customer paying in ETH already has what they need to cover the transaction fee; a customer paying in an ERC-20 token still spends ETH on gas to move the token. That is a customer-side cost on a public network — never a halfin charge — but it is worth knowing when you decide which routes to surface.
- Native ETH — the chain's own asset, settled as a direct balance transfer.
- USDT (ERC-20) — dollar-denominated stablecoin, the Ethereum issuance of USDT.
- USDC (ERC-20) — dollar-denominated stablecoin available on Ethereum, Base, and Solana.
- Other ERC-20 tokens — accepted as standard token transfers to the invoice address.
Confirmations and reorg-aware crediting
Ethereum reaches finality differently from a chain like Bitcoin. Under proof-of-stake, blocks are produced on a fixed cadence and become economically final after they are justified and finalized by the validator set, which spans a couple of epochs. In between, a short reorganization can still reorder or drop recently included transactions. A payment surface that credited on the first block it saw would occasionally mark an invoice paid against a transaction that later got unwound.
halfin does not do that. Crediting on Ethereum waits for the per-chain confirmation threshold before an invoice flips to paid, and it is reorg-aware: if a chain reorganization unwinds a transaction that was already seen, halfin reflects that rather than pretending the payment is still good. The amount you see as settled is an amount that actually held on-chain. The same logic applies whether the payment was native ETH or an ERC-20 token transfer.
For your integration this means the lifecycle is observable and trustworthy. A deposit appears, confirmations accumulate toward the threshold, and only then does the invoice settle and a signed webhook fire. You do not release goods on a payment that is merely seen — you wait for the paid event, which on Ethereum means confirmations that the network has effectively committed to.
The Ethereum and EVM L2 routing map
Ethereum mainnet is the canonical home for ETH and these ERC-20 stablecoins, but it is not always the cheapest place to move them. EVM L2s — Base, Arbitrum, and Polygon — are separate networks that settle to Ethereum while charging a fraction of mainnet gas. USDC in particular is available on Base, which makes it a natural lower-cost route for a stablecoin payment that would otherwise pay mainnet fees.
These are distinct on-chain networks, not a single "Ethereum" bucket — a USDC payment on Base is a different deposit on a different chain from a USDC payment on Ethereum mainnet, and halfin tracks each on its own gate with its own confirmation behavior. The table below is the real supported surface for Ethereum and the EVM L2s; an invoice draws from it rather than from a longer aspirational list.
| Network | Native asset | Stablecoins (gates that exist) | Why a payer picks it |
|---|---|---|---|
| Ethereum (mainnet) | ETH | USDT (ERC-20), USDC (ERC-20) | Where most ETH and ERC-20 stablecoin balances actually live. |
| Base | ETH (L2 gas) | USDC | Lower-cost USDC route that settles to Ethereum. |
| Arbitrum | ETH (L2 gas) | — | Cheaper EVM execution for native-asset payers already on Arbitrum. |
| Polygon | Native chain asset | — | Low-fee EVM route for payers who already hold value on Polygon. |
Billing in fiat, settling in ETH or a token
Whatever Ethereum asset the customer chooses, the figure you care about is the fiat one. You create a halfin invoice with an amount and a fiat currency — say 120.00 USD — and halfin computes the payable token amount from the live rate and pins it when the invoice activates. The customer sees a concrete number to send, in ETH or USDT or USDC, and you reconcile back to the 120.00 USD you billed.
Stablecoins make that anchor almost trivial, because a dollar-denominated token tracks the dollar — a USDC amount and a USD amount move together. Native ETH is more volatile, which is exactly why the rate lock matters: the ETH amount is fixed at activation and bounded by an expiry window, so the customer cannot pay against a stale quote and you are not exposed to a price that moved between draft and payment. If the window lapses, the invoice expires rather than silently re-pricing, and you can re-issue at the current rate.
Underpaid and overpaid amounts are handled the same way they are everywhere on the platform: an Ethereum deposit that arrives short is recorded as a shortfall against the invoice, and an overpayment is recorded as visible excess, rather than either being stranded as an unmatched transfer. The fiat anchor plus explicit under/over handling is what makes month-end reconciliation mechanical instead of a screenshot-matching exercise.
Create an Ethereum-payable invoice
Accepting Ethereum is the same spec-first REST flow as any other asset — you do not integrate a chain, you create an invoice and let the customer settle in what they hold. Authenticate with a scoped API key, post the fiat amount and currency, and pass an idempotency key so a retried request never creates a duplicate. halfin returns an invoice you present through hosted checkout or render yourself; the customer then pays in ETH or an ERC-20 token to the address shown.
The amount is sent as a string — monetary values are strings end to end, never floats — and the currency is your fiat anchor, not a token symbol. The exact request and response fields are defined at docs.thehalfin.com and in the @halfin/sdk-merchant types; the point of the call below is that one authenticated request gives you an invoice that a customer can settle on Ethereum.
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": "Annual license",
"idempotency_key": "00000000-0000-4000-8000-000000000002"
}'
# halfin locks the rate at activation and returns an invoice the customer
# can pay in native ETH or an ERC-20 token (USDT, USDC). Crediting waits
# for Ethereum's confirmation threshold and is reorg-aware. The full
# response schema lives at docs.thehalfin.com.Where Ethereum fits in the platform
Accepting Ethereum is rarely the whole job. The invoice a customer pays in ETH or USDC is what a hosted checkout renders, and the same object is available if you build the payment page yourself. When the invoice settles, a signed webhook tells your backend — verify the HMAC signature before you act, then fulfill, email, or update your records.
On the settlement side, ETH and ERC-20 stablecoins that land on a paid invoice accrue to a balance you can convert or pay out, so an Ethereum-denominated receivable does not have to stay denominated in the asset it arrived as. If your business pattern is a persistent receive address rather than a per-invoice flow, static deposit addresses cover that. And because USDT and USDC each span more than just Ethereum, the asset pages for those stablecoins are the place to compare routes when you want the cheapest network for a given payment.