The problem: a payment page is more than a form
A crypto payment page looks simple from the outside — show an address, wait for money. The reality is that each chain has its own address format, its own confirmation behaviour, and its own failure modes. A customer can underpay, overpay, send on the wrong network, or close the tab mid-payment and come back an hour later expecting the same address to still work.
Handle all of that yourself and you are maintaining wallet-connect libraries, QR generation, address rotation, a per-chain confirmation watcher, and a polling loop that survives a customer refreshing the page on a flaky mobile connection. None of it is your product, and all of it breaks quietly the day a chain changes behaviour.
Hosted checkout moves that surface off your stack. You create an invoice, send the customer to the returned checkout URL, and halfin owns the page from there: rendering, wallet flows, the address shown, and the payment state the customer sees. You get the customer back, paid or expired, with a webhook telling you which.
How it fits: one redirect, then a webhook
Hosted checkout sits on top of the invoicing primitive. You create an invoice through the REST API or the dashboard, and the response carries a hosted checkout URL on checkout.thehalfin.com. You redirect the customer there — a full-page redirect or a new tab, whichever fits your flow.
On that page the customer chooses a network from the ones you accept, sees the exact amount in the chosen asset, a QR code, and the deposit address. halfin watches the chain for the payment, applies the per-chain confirmation threshold, and updates the page in place as the payment moves from waiting to detected to confirmed.
When the invoice resolves, two things happen: the customer is returned to the success or cancel URL you configured, and your server receives an HMAC-signed webhook. The webhook is the source of truth for your business logic — verify the signature, then mark the order paid. The redirect is for the customer's eyes; the webhook is for your database.
- Create an invoice → get a checkout URL back.
- Redirect the customer to checkout.thehalfin.com.
- halfin renders the page, watches the chain, and shows live status.
- Customer returns to your success/cancel URL; your server gets a signed webhook.
What the customer sees
The page leads with one thing: the amount due, in the asset and network the customer selected. Below it sits the QR code and the address, with copy-to-clipboard for customers paying from a desktop or a separate device. The networks you accept are presented as a clear choice, not a wall of logos — the customer picks the rail they already hold funds on.
Payment state is visible the whole time. A waiting state shows the address and a countdown to expiry. Once a transaction is seen on-chain, the page reflects that it has been detected and is confirming, then shows confirmed once the per-chain threshold is met. The customer never has to guess whether their payment 'went through' — the page tells them, and so does the QR/address combination they can re-scan if a wallet hiccups.
Underpayment and overpayment are handled explicitly rather than left as a dead end. The invoicing layer records what was actually received against what was quoted, so a customer who sends slightly too little or too much lands in a defined state your back office can act on, instead of a silent mismatch you only discover at reconciliation.
Supported networks and assets on the page
The customer chooses from the rails you have enabled. halfin runs real on-chain gates for each one — there is no placeholder network on the page that quietly does nothing. Crediting is reorg-aware and each chain applies its own confirmation threshold, so a payment shown as confirmed on the page has actually settled under that chain's rules.
| Network | Native asset | Stablecoins on this network |
|---|---|---|
| Bitcoin | BTC | — |
| Ethereum | ETH | USDT (ERC-20), USDC (ERC-20) |
| Base | ETH | USDC |
| Arbitrum | ETH | — |
| Polygon | native gas token | — |
| BNB Smart Chain | BNB | — |
| Tron | TRX | USDT (TRC-20) |
| XRP Ledger | XRP | — |
| Solana | SOL | USDT (SPL), USDC (SPL) |
What hosted checkout takes off your plate
The point of hosting the page is the work you do not do. There is no wallet SDK to integrate and keep current, no QR library to ship, and no per-chain confirmation logic to write and test. Because you never render a card field or store card data, the page does not pull a card-data compliance burden into your stack — the customer pays from their own wallet, on-chain.
It also means you are not on the hook for the long tail of payment UX: the customer who reloads, the wallet that broadcasts twice, the network that takes longer to confirm than usual. halfin's page and the gates behind it absorb those cases. Your integration stays small — create the invoice, redirect, handle the webhook.
- No wallet-connect or QR code to build or maintain.
- No per-chain confirmation watcher in your codebase.
- No card data, so no card-data compliance scope from this page.
- No bespoke handling for refresh, double-broadcast, or slow confirmations.
- Your integration is a create call, a redirect, and a webhook handler.
Create an invoice and redirect
The integration is small enough to show in full. Create a deferred, fiat-anchored invoice, then send the customer to the hosted checkout URL in the response — the payer chooses the asset and network on that page. Point a webhook at your server so your database learns the outcome independently of the redirect.
Treat the redirect as cosmetic and the webhook as authoritative. A customer can pay and then close the tab before the redirect fires; the signed webhook still arrives, and that is what should flip the order to paid. Always verify the webhook signature before you act on it.
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 10472",
"idempotency_key": "order-10472"
}'
# The response includes a hosted checkout URL on checkout.thehalfin.com.
# The payer picks the asset and network on that page; see the full
# request schema at docs.thehalfin.com.
# Redirect the customer there, then wait for the signed
# invoice webhook on your server before marking the order paid.When to reach for self-hosted checkout instead
Hosted checkout is the right default when you want the smallest integration and are comfortable handing the page off to a halfin domain. If you need the payment UI to live entirely inside your own product — your own domain, your own layout, no redirect away — self-hosted checkout renders against the same API and the same invoicing primitive, and you keep control of the page at the cost of building and maintaining it.
If you do not need an interactive page at all — a machine paying a machine, or a recurring deposit address you hand out once — static deposit addresses and the M2M settlement flow cover those without a checkout page in the loop. Hosted checkout is specifically for the case where a human is on the other end and you want them on a page that just works.