Price in fiat, settle in crypto
Most businesses still think in their home currency. Your contracts, your accounting, and your pricing are denominated in USD or EUR, even when the payment arrives as Tron USDT or on-chain ETH. A crypto invoice that only quotes a token amount pushes the conversion work — and the FX risk — onto you.
A halfin invoice carries two figures that stay in lockstep: the fiat amount you actually care about, and the asset amount the customer pays. You create the invoice with the fiat amount and currency; halfin computes the payable token amount from the live rate and pins it for the life of that invoice. The customer sees a concrete number — "pay 49.91 USDT" — and you see the USD figure that lands in your ledger.
Because the fiat number is the source of truth, your reconciliation is mechanical. Every confirmed invoice maps back to the amount you billed, matched by the asset and the network it arrived on. You are not eyeballing rate screenshots at month-end to figure out what a payment was worth.
The rate locks when the invoice goes live
Crypto prices move on a timescale that matters for a checkout. If you quote a rate at draft time and the customer pays twenty minutes later, the token amount can drift enough to leave you short — or to leave them overpaying. halfin closes that window by locking the quote at activation, not at creation.
When an invoice activates, halfin freezes the conversion rate and stamps an expiry. The customer has a defined window to pay at that exact quote. If they pay inside the window, the asset amount they were shown is the asset amount that settles to your billed fiat figure. If the window passes, the invoice expires rather than silently re-pricing against a stale rate — you decide whether to re-issue at the current rate.
This is the difference between a payment request and a guess. The lock plus the expiry together mean nobody is exposed to a rate they did not agree to: not you, not the customer.
- Quote locked at activation — the payable token amount is fixed, not recalculated mid-payment.
- Bounded expiry — every invoice has a payment window, so you never honor yesterday's rate.
- Expiry is explicit — a lapsed invoice is marked expired instead of re-pricing in the background.
- Re-issue on your terms — generate a fresh invoice at the current rate when a customer comes back late.
Underpaid and overpaid amounts are handled, not lost
Real customers fat-finger amounts, send from exchanges that skim a withdrawal fee, or pay a slightly stale quote. A naive integration treats anything that is not an exact match as a failure and strands the money. halfin treats the gap as a first-class outcome.
Each invoice tracks the amount expected against the amount actually received on-chain. An underpayment is surfaced as exactly that — the invoice records the shortfall so you can request a top-up or settle partially per your own policy, rather than pretending the funds never arrived. An overpayment is likewise recorded against the invoice, so the excess is visible and accountable instead of vanishing into an unmatched-deposit pile.
Crediting itself is reorg-aware and respects per-chain confirmation thresholds: halfin waits for the network-appropriate number of confirmations before marking an invoice paid, and a chain reorganization that unwinds a transaction is reflected rather than ignored. The amount you see as settled is an amount that actually held.
One invoice, the full lifecycle
An invoice moves through a defined progression, and the value of the product is that every stage is observable the same way across the dashboard, the REST API, and webhooks. You are never guessing where a payment is.
The stages below are driven by what happens on-chain — a deposit appearing on the network, confirmations accumulating to the chain's threshold, or the expiry window elapsing — and each change is something your integration can react to via a signed webhook event.
| Stage | What it means | What you do |
|---|---|---|
| Awaiting payment | Invoice is live, rate locked, waiting for payment before expiry. | Show the customer the address, amount, network, and countdown. |
| Payment seen | A matching deposit has appeared on-chain but is not yet sufficiently confirmed. | Tell the customer the payment is in flight; do not release goods yet. |
| Confirming | Confirmations are accumulating toward the per-chain threshold. | Wait — crediting is reorg-aware and not yet final. |
| Paid | Threshold met; the billed fiat amount is settled to your balance. | Fulfill the order; reconcile against the original fiat figure. |
| Expired | The expiry window elapsed before a sufficient payment arrived. | Re-issue at the current rate if the customer still wants to pay. |
Multi-currency without multiplying your work
A customer in one region wants to pay TRC-20 USDT; another reaches for USDC on Base; a third only holds BTC. You should not have to build and maintain a separate flow for each. A single invoice can present supported networks and assets, and the customer settles in whichever one they hold — the fiat anchor stays the same regardless of how they pay.
halfin supports stablecoins and native assets across the chains where the gates actually run. The matrix below is the real supported surface; an invoice draws from it rather than from a longer aspirational list.
| Asset | Networks | Typical use |
|---|---|---|
| USDT | Tron (TRC-20), Ethereum (ERC-20), Solana | Lowest-friction stablecoin billing |
| USDC | Ethereum (ERC-20), Base, Solana | Stablecoin billing for US / EVM-native payers |
| BTC | Bitcoin | Customers paying from cold storage or BTC-only wallets |
| ETH | Ethereum + ERC-20 tokens | EVM-native payers and token settlement |
| SOL | Solana (SOL + SPL) | Fast, low-fee settlement |
| XRP | XRP Ledger | Fast finality on the XRP Ledger |
| Native L2 / chain assets | Base, Arbitrum, Polygon, BNB Smart Chain | Pay where the customer already holds value |
Create one with a single call
Invoicing is spec-first REST. You authenticate with a scoped API key and post the fiat amount and currency; halfin computes the payable asset amount and hands back an invoice you can present through hosted checkout or render yourself. Pass an idempotency key so a retried request never creates a duplicate invoice.
Here is a minimal create call against the public API. The amount is sent as a string — monetary values are strings end to end, never floats — and the currency is your fiat anchor. The exact response shape is defined in the docs and the @halfin/sdk-merchant types; the point here is that one authenticated call gives you a payable invoice tied to the fiat figure you billed.
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": "Pro plan — March",
"idempotency_key": "00000000-0000-4000-8000-000000000001"
}'
# halfin locks the rate at activation, pins the payable asset amount,
# and returns an invoice you present to the customer and then track via
# signed webhook events. See docs.thehalfin.com for the full response schema.Where invoicing fits in the platform
Invoicing rarely stands alone. The invoice you create here is what a hosted checkout renders for the customer, and the same object is available if you build the payment UI yourself with self-hosted checkout. When an invoice changes state, a signed webhook tells your backend — verify the HMAC signature before you act on it, then fulfill, email, or update your own records.
On the settlement side, paid invoices accrue to a balance you can convert or pay out. A customer dispute or a goodwill gesture flows through refunds. If you need a persistent receive address rather than a per-invoice flow, static deposit addresses cover that pattern. The point is that one fiat-anchored primitive plugs into the rest without re-modeling money at each step.