Hosted Checkout
Send a URL, take payment. 0fra handles the wallet UX so you don't have to.
A Checkout Session is a single-use URL where your customer pays. 0fra hosts the page, prompts the customer for their wallet, and walks them through approve + payOrder on-chain. You only need three moving pieces:
- Create the session via API → get a
url - Redirect the buyer to that URL
- Handle the success/cancel return +
order.confirmedwebhook
Create a session
curl -X POST https://api.0fra.dev/v1/checkout/sessions \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"merchant_id": "vendor_2001",
"order_no": "ORDER-1001",
"chain_id": 84532,
"token": "USDC",
"amount": "100.00",
"success_url": "https://acme.example/success?order=ORDER-1001",
"cancel_url": "https://acme.example/cart",
"metadata": { "ticket_type": "VIP" }
}'{
"id": "...",
"session_token": "cs_live_4f3...",
"url": "https://dash.0fra.dev/checkout/cs_live_4f3...",
"state": "open",
"order_id": "...",
"expires_at": "2026-04-25T18:30:00Z"
}| Field | Required | Notes |
|---|---|---|
merchant_id | yes | The external_id you assigned when onboarding |
order_no | yes | Unique within your platform — your own order number |
chain_id | yes | 84532 (Sepolia) or 8453 (mainnet, beta) |
token | yes | USDC for now |
amount | yes | Decimal string in human units ("100.00", not 100000000) |
success_url | no | Where to send the buyer after success |
cancel_url | no | Where to send them if they bail |
payer_address | no | Lock the session to a specific wallet address |
expires_in_seconds | no | Default 30 min; max 24 h |
metadata | no | Up to 5 KB of JSON — echoed back in webhooks |
What the buyer sees
The hosted page lays out:
- The price and the breakdown of fees (so the buyer knows what's going to whom)
- The merchant's display name
- A wallet connect button
- Two-step flow:
- Step 1 — Approve USDC to PaymentRouter (one click; one-time per token)
- Step 2 — Sign and broadcast
payOrder()(one click)
- A live confirmation badge once the tx is mined
Polling vs webhooks
Don't poll the session for state from your backend. Instead:
- Backend: subscribe to
order.confirmed/order.failedwebhooks - Front-end after redirect: read the session via the public endpoint
GET /v1/checkout/sessions/:token (no auth)Returns state (open / completed / expired) and the underlying order. Suitable for "show pending..." spinners on your success page.
Expiry
Sessions auto-expire when expires_at passes. The page renders a friendly "expired" state and the underlying order goes to expired.
You can't extend an expired session — generate a new one (with the same order_no if your business logic allows).
Embedding without a redirect
Today the only flow is full-page redirect. SDK / iframe embeds are on the roadmap.