Skip to main content
A checkout session represents one customer’s attempt to pay you in crypto. It is single-use: once created it is assigned a unique deposit address, an expiry timer starts, and the session moves through a series of statuses as the chain is monitored for the expected payment. When the payment is confirmed, the session is marked paid and your webhook is fired.

Two creation modes

You can create a checkout session in two ways depending on whether the amount is already known.

Link mode

Pass a linkId pointing to an existing payment link. The session inherits the link’s fiatAmount and fiatCurrency. Use this mode when customers arrive via a payment link URL.

Ad-hoc mode

Pass an amount object with value and currency directly. Use this mode when you calculate the price server-side per order, e.g. in an e-commerce checkout flow.

Key fields

FieldTypeDescription
idstringUnique session identifier (cs_…).
object"checkout_session"String literal identifying the object type.
linkIdstring | nullThe ID of the payment link that spawned this session, or null for ad-hoc sessions.
statusstringCurrent status of the payment — see status values below.
addressstringThe deposit address the customer must send funds to.
amount{ wei, eth }The crypto amount expected, expressed in the chain’s smallest unit and a human-readable denomination.
fiat{ amount, currency }The original fiat amount and currency this session was created for.
ethPriceUsdstringThe native-coin/USD exchange rate locked in at session creation time.
expiresAtstringISO 8601 timestamp after which the session becomes expired if unpaid.
paidAtstring | nullTimestamp when the payment reached the required confirmation count.
txHashstring | nullThe on-chain transaction hash of the payment, once detected.
chainIdnumberThe EVM chain ID (or synthetic ID for non-EVM networks) the session is on.
metadataobject | nullArbitrary key-value data you attached at creation.
successUrlstring | nullRedirect URL for the customer after a successful payment.
cancelUrlstring | nullRedirect URL if the customer abandons the checkout.
returnUrlstring | nullA general-purpose return URL used when the customer clicks “back” on the hosted page.
customerEmailstring | nullCustomer’s email address, if collected.
livemodebooleantrue for live payments, false for test-mode sessions.
checkoutUrlstringThe hosted page URL to redirect your customer to.
createdAtstringISO 8601 timestamp when the session was created.
updatedAtstringISO 8601 timestamp when the session was last modified.

Status values

StatusMeaning
pendingSession created and waiting for a payment to be detected on-chain.
detectedA transaction to the deposit address has been seen with the correct amount, but confirmation threshold not yet reached.
underpaidA payment was received but the amount was less than expected.
overpaidA payment was received but the amount exceeded what was expected.
paidPayment confirmed with the required number of block confirmations. Terminal — triggers your session.paid webhook.
paid_latePayment confirmed after the session’s expiry time but within the grace window. Terminal.
expiredThe session TTL elapsed with no payment detected. Terminal.
failedA terminal failure occurred (e.g. unrecoverable chain error).
See Session Lifecycle for a complete walkthrough of how sessions transition between these statuses.

Session TTL and the grace window

Every session has a configurable expiry time set by your operator. After expiresAt is reached:
  • The session moves to expired if no payment has been detected.
  • However, a late-payment grace window remains open for a configurable period after expiry. If a valid payment confirms within this window, the session transitions to paid_late rather than staying expired.
Always communicate the expiry time to your customers on your checkout page. A customer who sends funds after the grace window has closed will not automatically receive credit — you’ll need to handle the reconciliation manually.

Creating a session via the API

curl -X POST https://your-domain.com/api/v1/checkout_sessions \
  -H "Authorization: Bearer ck_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-123-v1" \
  -d '{
    "amount": {
      "value": "99.00",
      "currency": "USD"
    },
    "successUrl": "https://example.com/ok",
    "metadata": {
      "orderId": "123"
    }
  }'
Always supply an Idempotency-Key header with a unique value per payment attempt. If your server retries the request due to a network error, Crypto Checkout will return the original session instead of creating a duplicate.

Redirecting the customer

After creating a session, redirect the customer to session.checkoutUrl. The hosted checkout page displays:
  • The deposit address as a QR code and copyable string.
  • A live countdown timer to expiresAt.
  • The exact crypto amount to send (converted from fiat at the locked-in rate).
  • Real-time status updates as the payment is detected and confirmed.
// Example: redirect after session creation
const session = await cryptoCheckout.checkoutSessions.create({
  amount: { value: "99.00", currency: "USD" },
  successUrl: "https://example.com/ok",
  metadata: { orderId: "123" },
});

// Redirect your customer to the hosted checkout page
res.redirect(session.checkoutUrl);

Polling vs. webhooks

You have two options for knowing when a session has been paid:

Webhooks (recommended)

Register a webhook endpoint and listen for the session.paid event. Webhooks are pushed instantly when the payment confirms and require no polling loop. See Webhooks Overview.

Polling

Call GET /api/v1/checkout_sessions/{id} repeatedly and check the status field. Suitable for low-volume use cases or debugging, but less efficient than webhooks.
# Retrieve a session to check its current status
curl https://your-domain.com/api/v1/checkout_sessions/cs_abc123 \
  -H "Authorization: Bearer ck_test_YOUR_KEY"

Next steps

Session Lifecycle

See the full status state machine and detection flow.

Checkout Sessions API reference

Full request/response schemas for every checkout sessions endpoint.