Checkout

Merchant checkout sessions. The customer pays in crypto; the payment is recorded in your ledger as a stablecoin credit, which can't be spent or withdrawn yet. In sandbox today.

POST /api/v1/checkout/sessions

Bearer apk_ · write:checkout · Idempotent

{
"amountUsdCents": 2500,
"description": "Pro plan · 1 seat",
"merchantReference": "order-1234",
"successUrl": "https://yoursite.com/thanks",
"cancelUrl": "https://yoursite.com/cart",
"acceptedAssets": ["USDC", "USDT"],
"acceptedChains": ["ethereum", "polygon", "arbitrum", "base", "solana"],
"expiresInSeconds": 1800,
"settlementOption": "hold_stablecoin"   // "convert_to_fiat" is accepted but does nothing yet
}

Returns { session: { id, status, hostedUrl, receivingAddresses, expiresAt, … } }. Send the customer to hostedUrl.

successUrl and cancelUrl are optional http(s) addresses on your site. After paying, the customer is sent to successUrl; without one the hosted page shows that the payment was received. cancelUrl is the "Back to …" button on an expired link, so make it a page for someone who hasn't paid (your shop or cart, not a thank-you page). expiresInSeconds is 300 to 86400 (default 1800).

acceptedChains is optional. Left out, the session takes those of ethereum, polygon, arbitrum, base and solana that your acceptedAssets can be paid on, and for a coin none of them takes (BTC), the networks it can be paid on. Listed, a network none of your coins can be paid on is left out (["USDT"] on those five networks takes ethereum, polygon, arbitrum and solana); the returned acceptedChains is the list the session kept.

400 when amountUsdCents is over 10000000 ($100,000.00), or when a coin in acceptedAssets can't be paid on any of the acceptedChains: the message names the networks the coin works on. SOL is refused on every network for now (its payments can't be detected yet). Coins and networks lists every pair.

GET /api/v1/checkout/sessions

Bearer apk_ · read:checkout

Last 50 sessions.

GET /api/v1/checkout/sessions/[id]

Bearer apk_ · read:checkout

Full row, merchant-side. A link you cancelled on Get paid has expiresAt set to the moment you cancelled it and metadata.sorenpay_cancelled_at (the same time); the rest of your metadata is kept. Its status turns expired as for a link that ran out of time: at once when no customer picked a way to pay, otherwise once the payment watcher has taken a last look on chain (within a minute). Until then it can't be paid or picked, like any link past its time.

GET /api/v1/checkout/sessions/[id]/status

Public

Customer-safe status poll — used by the hosted page. An unpaid link past its time answers expired. The chosen coin, network and address are returned only once a payment is seen on chain: until then each visitor's choice stays their own. acceptedAssets, acceptedChains and receivingAddresses list only what can still be paid (a link made before a coin or network pair was dropped keeps it in its row, not here). completedAt is when a paid link was paid. cancelled is true for an expired link you cancelled. For an hour after a link ends (expired or cancelled), a payment sent before it ended still moves it on to confirming and completed.

POST /api/v1/checkout/sessions/[id]/select

Public

Customer picks (asset, chain) and gets that pair's receiving address. Starts the on-chain confirmation watcher, which watches every receiving address of the session: a payment is recorded on the pair whose address it reached, even if another visitor picked a different pair later. 409 once a payment has been seen, 410 once the link has ended (cancelled too), 400 pair_not_supported for a pair a link made before the pair was dropped still lists (USDT on Base, ETH on Polygon, SOL).

{
"asset": "USDC",
"chain": "polygon"
}

POST /api/v1/checkout/sessions/[id]/simulate

Bearer apk_ · write:checkout · sandbox only

Completes the session without a real blockchain payment. Refused on a live deployment or a live workspace. 409 link_expired for an expired or cancelled link, 422 asset_chain_not_selected before a coin and network were picked on the hosted page.

State machine

pending → awaiting_payment → confirming (n/3) → completed | underpaid | failed
                 ↓                  ↑
              expired ──────────────┘
   (TTL, or cancelled on Get paid. A payment sent before the link
    ended and found within an hour of its end still moves it on. A
    payment seen that never confirms ends expired a day after the TTL.)

Outbound webhook

checkout.session.completed fires to every active workspace endpoint subscribed to that event (or *). Carries { id, amountUsdCents, txHash, payerAddress }.