Sessions
A CheckoutSession is what the buyer interacts with: a time-limited, single-use object that owns the hosted checkout URL. It’s the lightest of the three core entities. Most of your logic works with Orders and Payment Intents (see below).
The three-entity data model
CheckoutSession ← 1:1 → Order ← 1:N → PaymentIntent
(buyer) (catalog) (each pay attempt)| Entity | Purpose | Lifetime |
|---|---|---|
| CheckoutSession | Buyer-facing — has session_key, checkout_url, TTL | Minutes (default 30) |
| Order | Your catalog state — line items, totals, refunds | Permanent record |
| PaymentIntent | One pay attempt on one chain/asset | Hours; settles or expires |
You create a session and an order together (via POST /b2b/v1/checkout-sessions/quick).
Each time the buyer picks an asset on the checkout page, a fresh
PaymentIntent is opened against the right chain. If they switch
assets mid-checkout, the previous intent goes to EXPIRED and a new
one starts.
Identifier
A session is identified by its session_key:
cst_G-SO92J7HNWkwMHEHjD4oO1ZURL-safe, about 24 characters after the prefix. The hosted checkout page is
https://checkout.infraio.xyz/<session_key> — treat the session key
as a bearer credential for that one checkout.
Lifecycle — CheckoutSession
| State | Means |
|---|---|
ACTIVE | Session created and open. The checkout URL is usable. |
COMPLETED | A PaymentIntent on this session settled. The Order is now PAID (or PARTIAL_PAID if underpaid). |
EXPIRED | expires_at passed without settlement. Any open Orders are canceled. |
CANCELED | Explicit cancel — either buyer hit “cancel” or you called the cancel endpoint. |
The three terminal states are mutually exclusive and final. A new CheckoutSession against the same Order can be created if you want to retry (e.g., after underpayment).
TTL
- Default: 30 minutes (configurable via the
expires_infield on create, in seconds). - Bounds: There is no enforced minimum or maximum. Choose a value that matches your buyer’s expected decision window: under 60 seconds risks timing out legitimate buyers, and a session that stays open for more than 7 days is almost certainly abandoned.
- Per-merchant default: You can set a default in the dashboard, but
only
POST /b2b/v1/checkout-sessions(two-step) honors it.POST /b2b/v1/checkout-sessions/quickuses 30 minutes whenexpires_inis omitted, regardless of the dashboard setting. To use a different default with/quick, sendexpires_inon every call. - Enforcement: A session whose
expires_athas passed is treated asEXPIREDeven if its state hasn’t updated yet, so don’t rely on the state value at the exact moment of expiry.
Underpayment
If a buyer sends less than the session amount, the PaymentIntent
still settles for the partial amount and the Order transitions to
PARTIAL_PAID. The CheckoutSession moves to COMPLETED (one
PaymentIntent settled), so it’s no longer reusable.
To accept the shortfall as full payment, the order can be resolved to
PAID. See Concepts → Orders (resolving isn’t
available through the API; to stay self-serve, collect the remainder instead).
To collect the remainder, create a new CheckoutSession against
the same Order with the residual amount.
Overpayment
If the buyer sends more than the session amount (rare, but happens with manual transfers), InfraIO Pay detects the overpayment within 24 hours and notifies you. It isn’t refunded automatically. Issue the refund through the refund API or the dashboard.
Wrong-asset payments
The deposit address is generated for one session, network, and asset. If a buyer sends a different asset to it, the payment isn’t matched and the PaymentIntent stays open until the session expires. Support can help recover the funds, but it isn’t automatic. Tell buyers to send the exact asset shown on the checkout page.
On TRON, Solana and TON there is no deposit address, so this applies to EVM networks only: the buyer pays your wallet directly. See Direct-to-wallet networks.
Idempotency on create
POST /b2b/v1/checkout-sessions/quick accepts an idempotency_key
field in the request body (note: body field, not HTTP header).
Generate a UUID if you don’t have a natural key.
The key deduplicates the Order, not the CheckoutSession. On a
retry with the same key, /quick returns the original order
(order_id is stable) but mints a fresh CheckoutSession — a new
session_key and checkout_url each time. That’s intentional: one
Order can back several checkout attempts (see
Orders), so a retried /quick hands the buyer
a clean session without duplicating the order.
Two behaviors to be aware of:
- The body is not hashed or compared. Reusing a key with a
different body does not return
409— the server silently returns the order already stored under that key and ignores the new body. So treat anidempotency_keyas a one-shot token for a single logical order; never recycle one across different carts. - Only the Order is deduplicated, not the session. If you need the
same checkout URL back, persist
session_key/checkout_urlfrom the first response — calling/quickagain won’t return the old one. To enumerate every session minted against an order, useGET /b2b/v1/checkout-sessions/by-order/:order_id.
API endpoints
| Method | Path | Notes |
|---|---|---|
POST | /b2b/v1/checkout-sessions/quick | Create order + session in one call |
POST | /b2b/v1/checkout-sessions | Create session against an existing order |
GET | /b2b/v1/checkout-sessions/by-order/:order_id | List all sessions for an order (for retry history) |
See the Quickstart for the full create request body and signing.
What’s next
- Concepts → Orders — the Order entity (the one you treat as ground truth for fulfillment).
- Concepts → Chains & assets — supported networks and finality assumptions per chain.
- Webhooks → Overview — which events fire at each session state transition.