BTC$68,420▲ 0.82% / ETH$3,841▼ 0.31% / LTC$94.05▲ 1.14% / SOL$221.60▲ 3.02% / USDT·TRC20$1.0000 / USDC·ERC20$0.9999 / XMR$168.42▼ 0.18% / TON$6.02▲ 0.47% / XRP$2.41▼ 0.09% / GAS·ETH18 gwei / Indicative prices · illustrative ticker / No-KYC · non-pooled · 0.4% flat / BTC$68,420▲ 0.82% / ETH$3,841▼ 0.31% / LTC$94.05▲ 1.14% / SOL$221.60▲ 3.02% / USDT·TRC20$1.0000 / USDC·ERC20$0.9999 / XMR$168.42▼ 0.18% / TON$6.02▲ 0.47% / XRP$2.41▼ 0.09% / GAS·ETH18 gwei / Indicative prices · illustrative ticker / No-KYC · non-pooled · 0.4% flat
OrbChain
UTC 18:47:02 Get started →
Feature · Hosted checkout ▓ /pay/:trackId Server-rendered, CSP-strict

The page
your customers meet.

One URL per invoice — /pay/<trackId>. Server-rendered HTML, server-side QR generation, live countdown, coin picker with fiat pricing, no-JS fallback, strict CSP. The details a customer feels without knowing why: no spinner, no hydration flash, no blank-white page while the QR loads.

Server-rendered · Signed · Accessible
§ Part I / IV · The details

Six things that matter.
We got them all.

Craft
Customers don't know why; they feel it
SymDetailWhat we didStatus
QRServer-side QRPNG data URL generated during render, CSP-safe. No client fetch, no loading state, no skeleton flash on mobile. Lightning invoices get a lightning: deep-link variant.LIVE
CDLive countdownMM:SS countdown until invoice expiry. Turns amber in the last 5 minutes, red at expiry, then auto-reloads. Computed client-side from expiresAt so the server isn't pinged on every tick.LIVE
CPCoin pickerCustomers choose the coin at pay-time, not invoice-create time. Fiat-priced invoices lock the rate for 15 minutes at choose-coin. BIP-21 deep links per coin.LIVE
NJNo-JS fallbackCoin picker is a wrapped <form> that GET-submits ?pay_currency=X, the server mutates + 303-redirects. Works with JavaScript disabled — lets us hit the 'unsafe-inline'-free CSP.LIVE
CSPStrict CSPNonce-gated <script> tags. XSS into an injected script tag is blocked. Every resource self-hosted; no third-party trackers, no GA, no Intercom.LIVE
EXPClient-side expiryIf the DB still says WAITING but expiresAt < now, the page derives "Expired" client-side. Prevents the reload loop that stale invoices cause on slow DB ticks.LIVE
§ Part II / IV · Flow

One URL. Four states.
Zero spinners.

Flow
From invoice mint to success
SymStateWhat the customer seesNext
1CHOOSEInvoice has no pay_currency yet. Fiat amount hero ($50), coin picker grid with live-priced pay_amount per coin. Customer picks.→ 2
2WAITINGBig QR, copyable address, pay_amount in native unit, "Open in wallet" deep-link, live countdown, status dot pulsing.→ 3 or 4
3PAIDCheckmark animation, paid amount, optional merchant thanks_message, "Return to shop" button if return_url is set.END
4EXPIREDAmber notice explaining the invoice expired before payment arrived. Contact-merchant link if branding is configured.END
§ Part III / IV · Branding

Your brand on our page.
Not "powered by OrbChain" in the footer.

Config
Per-merchant fields
SymFieldWhere it showsDefault
LOGmerchant.logo_urlTop-left of the checkout. Replaces the OrbChain logo on your invoices. 512px recommended.OrbChain
DESmerchant.descriptionShort tagline under the merchant name on the hosted page. 140 chars.Empty
TKSmerchant.thanks_messageCustom message on the PAID screen. Markdown-safe."Thank you!"
RETinvoice.return_urlMerchant-provided per-invoice return destination. Button appears on PAID state.None
§ Part IV / IV · Questions

Things integrators
ask before embedding.

Answered
In full, with links
Q.01Can I self-host the checkout page?

Yes — use POST /v1/payment/white-label instead of invoice. You receive a track_id + deposit address and render the UI yourself. The hosted checkout is optional.

Q.02Does the hosted page work inside an iframe?

We set X-Frame-Options: SAMEORIGIN by default; iframing the page from a third-party origin is blocked. Use the white-label API if you need iframe embedding.

Q.03How do you handle Lightning invoices specifically?

The QR encodes a lightning:lnbc… URI; the "Open in wallet" button deep-links the same. On the pay page the countdown shows invoice expiry (typically 1h); settlement is atomic, so PAID appears within seconds of the payment being routed.

Q.04What happens if the customer's wallet under-pays?

The page stays in WAITING until either the full amount is received or the invoice expires. Over-payment credits the merchant fully; under-payment holds. If you expect partial payments (some subscriptions do), use static-address + per-user attribution instead of one-shot invoices.

Q.05Is the page accessible / WCAG-compliant?

Semantic HTML, keyboard-navigable forms, visible focus, high-contrast palette on dark. No critical functionality gated on JS. Screen reader tested on VoiceOver + NVDA for the WAITING and PAID screens.

The page customers see
is the page customers see.

No white-flash hydration, no third-party tracker on your checkout, no "powered by" footer. Branding you configured, infra we run.

§ Feature stack · hosted checkout

Every hosted page is backed
by the six named features.

Customer-facing
Merchant-configurable
SymFeatureMechanismStatus
LNInstant LightningAccept and pay out Bitcoin over Lightning. Atomic settlement, zero confirmations, sub-cent fees. On-chain fallback is one parameter.LIVE
ASAuto SettleCustomer pays any accepted coin. You always receive the settlement coin you configured — pay-with-BTC, get-USDT. Swap runs at checkout, no extra setup.LIVE
APAuto PayoutSet a balance threshold. We sweep to your cold wallet the moment you hit it. 30-minute cooldown on failure. No manual clicks, no scheduled cron.LIVE
SRSigned ReceiptsEvery PAID payment gets an Ed25519-signed receipt. Verify offline with our public key. No other gateway in our category ships this.LIVE
NKNo-KYC ForeverEmail + password, that's it. No documents, no verification wall, no volume gates. We don't touch your customers' identity — your gateway, your rules.LIVE
DWDeterministic WebhooksEvery webhook carries a SHA-256 event_id derived from (event_type, entity_id). Retry 1,000×, same id. True idempotency without an extra DB column.LIVE