The problem: a hosted page that feels like a dead end
The moment a customer leaves your domain for a payment page is the moment they're most likely to abandon. If that page is generic — no merchant name, no order context, a wall of networks they don't recognise, and no obvious way back — the customer pauses to wonder whether they're in the right place. On a crypto payment, where the action is irreversible, that hesitation costs you the sale.
The fix isn't to rebuild the page yourself. It's to control the parts of the hosted page that carry context: what the customer sees identifying the payment, which rails they're offered, and where they go next. halfin owns the rendering, the wallet flows, and the on-chain watching; you own the presentation around them. The redirect mechanics, the webhook, and the payment-state lifecycle belong to the hosted checkout itself — see the parent product for those.
What you control on the page
Customization is scoped to presentation and routing — the things that change how the page reads and where it sends the customer, not the wallet or confirmation logic underneath. You set these when you create the invoice, or as defaults on your account, and the hosted page renders them.
Several come straight from invoice fields you're already setting. The amount the customer sees is the amount you anchored the invoice to — a fiat-anchored figure for a deferred invoice, or a fixed asset amount — so the number on the page always matches what you charged. The description you pass becomes the order context the customer reads, so they can confirm they're paying for the right thing before they send anything.
| Control | Set via | What the customer sees |
|---|---|---|
| Merchant identity | Account branding | Your name and brand presented on the page |
| Amount due | Invoice amount fields | The exact fiat-anchored or fixed-asset amount you charged |
| Order context | Invoice description | A line identifying what the payment is for |
| Offered networks | Enabled rails on your account | Only the chains and assets you accept, as a clear choice |
| Return destination | redirect_url on the invoice | Where they land back in your store when the payment resolves |
| Expiry window | Invoice expiry | A countdown while the invoice is still open to pay |
Branding: make the page read as yours
The strongest signal that a customer is in the right place is seeing your name where they expect it. The hosted page presents your merchant identity — the name and brand on your account — at the top of the page, so the transition from your store to checkout.thehalfin.com reads as one flow rather than a jump to an unrelated service.
Brand presentation is an account-level setting, not something you repeat on every invoice. You configure it once in the dashboard and every hosted page inherits it — consistent across every order, with no branding threaded through your API calls.
Let the per-invoice description carry what branding can't: what this specific payment is for. Branding answers 'who am I paying'; the description answers 'what am I paying for'. Together they remove the two questions that make a customer hesitate on an unfamiliar page.
- Merchant name and brand come from your account, set once in the dashboard.
- Every generated hosted page inherits the same identity automatically.
- Per-invoice description carries the order-specific context.
Networks: show only the rails you accept
A customer paying in crypto already knows which asset and chain they hold. The hosted page's job is to offer exactly the rails you accept — no more — so the customer picks the one they already have funds on and moves on. You don't present a wall of every chain that exists; you present your enabled set.
Every network shown on the page is backed by a real on-chain gate. There's no placeholder rail that displays but quietly fails to settle. Crediting is reorg-aware and each chain applies its own confirmation threshold, so a network you enable is one halfin actually watches and confirms under that chain's rules.
Enable the rails that match where your customers hold funds and where you want to settle. The table below is the full set you can offer — you choose the subset that appears.
| 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) |
Return URL: close the loop back to your store
The last piece of presentation is where the customer goes when they're done. Set redirect_url on the invoice and the hosted page sends the customer back to your store once the payment resolves — point it at a thank-you or order-confirmation page so they never get stranded on a halfin domain wondering what happens next.
Treat that return URL as cosmetic, exactly as with the rest of the page. It gives the customer a clean exit, but it is not how your system learns the outcome. A customer can pay and close the tab before the redirect ever fires; your order state must come from the HMAC-signed webhook your server receives, which arrives independently. Verify the webhook signature, then mark the order paid — never drive fulfilment off the return URL alone.
- redirect_url — where the customer lands back in your store after the payment resolves.
- It is for the customer's experience only, not a source of truth.
- Order state comes from the verified webhook, not the redirect.
Setting it on the invoice
Branding is configured once on your account; the rest of the presentation is set per invoice. The example below creates a deferred, fiat-anchored invoice with order context for the page and a redirect_url back to your store, then sends the customer to the hosted checkout URL in the response. The offered networks come from the rails enabled on your account.
Pass idempotency_key in the request body — a snake_case field, not a header — so a retried create call returns the same invoice instead of a second one. The only headers you send are X-API-Key and Content-Type. See the SDK reference for the typed equivalent of this call.
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 — Annual plan",
"redirect_url": "https://yourstore.example/orders/10472/paid",
"idempotency_key": "order-10472"
}'
# The response carries a hosted checkout URL on checkout.thehalfin.com.
# The page shows your account branding, the description above, and the
# networks you enable; redirect the customer there. On resolution the
# customer returns to your redirect_url, and your server gets a signed
# webhook — verify it before marking the order paid.What customization does not change
Customization is presentation and routing. It does not let you alter the wallet flows, the QR and address rendering, or the per-chain confirmation logic — those are the parts halfin hosts precisely so you never have to build or maintain them, and they stay consistent across every merchant's page.
If you need the payment UI to live entirely inside your own product — your own domain, your own layout, no redirect to a halfin page at all — reach for self-hosted checkout. It renders against the same invoicing API and gives you full control of the page, at the cost of building and maintaining it yourself. Hosted checkout customization is for the common case: you want the page recognisably yours, but you don't want to own the page.