Skip to main content
This guide walks you through the complete flow from creating a checkout session to receiving the session.paid webhook. Every integration, whether it’s a custom storefront or a WooCommerce plugin, follows this same loop. By the end you’ll have made a real (or testnet) payment and verified it lands correctly in both your backend and the dashboard.

Prerequisites

Before starting, make sure you have:
  • A running Crypto Checkout instance (self-hosted or deployed to Vercel)
  • A wallet configured in Dashboard → Settings → Wallet with a BIP39 mnemonic imported
  • At least one API key created in Dashboard → API Keys — copy the raw ck_test_... value shown at creation time, as it is never displayed again
  • (Optional) A publicly reachable webhook endpoint registered in Dashboard → Webhooks

End-to-end flow

1
Create a checkout session
2
Send a POST /api/v1/checkout_sessions request from your backend. Always include an Idempotency-Key header so that a network retry cannot create a duplicate session.
3
Never call this endpoint from the browser — your API key would be exposed. Create sessions server-side and redirect the customer to the returned checkoutUrl.
4
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-001-v1" \
  -d '{
    "amount": { "value": "50.00", "currency": "USD" },
    "metadata": { "orderId": "001" },
    "successUrl": "https://your-store.com/success"
  }'
5
Using the TypeScript SDK:
6
import { CheckoutClient } from "@your-org/checkout-sdk";

const client = new CheckoutClient({
  apiKey: process.env.CHECKOUT_API_KEY!,
  baseUrl: "https://your-domain.com",
});

const session = await client.checkoutSessions.create(
  {
    amount: { value: "50.00", currency: "USD" },
    metadata: { orderId: "001" },
    successUrl: "https://your-store.com/success",
  },
  { idempotencyKey: "order-001-v1" },
);
7
The API returns a checkout_session object:
8
{
  "id": "cs_01HZ...",
  "object": "checkout_session",
  "status": "pending",
  "checkoutUrl": "https://your-domain.com/checkout/cs_01HZ...",
  "address": "0xAbCd...1234",
  "amount": { "wei": "18473920000000000000", "eth": "18.47392" },
  "fiat": { "amount": "50.00", "currency": "USD" },
  "expiresAt": "2024-11-01T12:05:00.000Z",
  "livemode": false,
  "createdAt": "2024-11-01T12:00:00.000Z"
}
9
Save the session id in your order record alongside the expiresAt timestamp.
10
Redirect the customer
11
Redirect your customer’s browser to session.checkoutUrl. This is a hosted page served by your Crypto Checkout instance — you do not need to build any payment UI yourself.
12
// Next.js example
redirect(session.checkoutUrl);
13
Customer scans the QR code and sends crypto
14
The checkout page displays:
15
  • A QR code encoding the deposit address
  • The exact amount required (in both crypto and fiat)
  • A live countdown to expiry
  • 16
    The customer opens their wallet (MetaMask, Rabby, a mobile wallet, etc.), scans the QR code or copies the address, and sends exactly the displayed amount to the shown address on the correct chain. The page polls for status changes in real time and advances automatically once payment is detected.
    17
    The customer must send the exact amount shown. Sending less leaves the session in underpaid; sending more moves it to overpaid. See the edge cases section below.
    18
    Payment is detected
    19
    As soon as the transaction is broadcast, Alchemy Notify POSTs to your Crypto Checkout instance. The session status transitions from pendingdetected almost immediately. The checkout page updates to show a confirmation counter.
    20
    Confirmations accumulate
    21
    Crypto Checkout waits 60 seconds, then re-reads the on-chain receipt and counts confirmations. Once the confirmation count reaches the required number of confirmations (default: 3), the session transitions from detectedpaid and the session.paid event fires.
    22
    You can increase the required number of confirmations in your environment for higher-value payments. For testnet development, lowering it to 1 speeds up the test cycle.
    23
    Handle the session.paid webhook
    24
    Your backend receives a POST from Crypto Checkout with the event type session.paid. Always verify the signature before fulfilling an order.
    25
    // Express.js handler
    import { verifyWebhook } from "@your-org/checkout-sdk/webhooks";
    
    app.post("/webhooks/crypto", express.raw({ type: "*/*" }), (req, res) => {
      const event = verifyWebhook({
        payload: req.body.toString(),   // raw string — do NOT JSON.parse first
        signature: req.headers["x-webhook-signature"] as string,
        secret: process.env.CHECKOUT_WEBHOOK_SECRET!,
      });
    
      if (event.type === "session.paid") {
        const session = event.data as { metadata?: { orderId?: string } };
        const orderId = session.metadata?.orderId;
        // Mark the order paid in your database, send confirmation email, etc.
        fulfillOrder(orderId);
      }
    
      res.sendStatus(200); // respond 2xx within the timeout window
    });
    
    26
    Crypto Checkout retries failed deliveries up to 6 times over approximately 31 hours using exponential backoff. Returning a non-2xx status or timing out counts as a failure and triggers a retry.
    27
    Verify via the dashboard
    28
    Open Dashboard → Sessions and find your session. Confirm:
    29
  • Status is paid (or paid_late if payment arrived just after expiry but within the grace window)
  • The transaction hash is populated
  • A sweep entry is visible in Dashboard → Payouts, confirming funds were forwarded to your payout wallet

  • Handling edge cases

    ScenarioSession statusRecommended action
    Session expired before paymentexpiredCreate a new session and redirect the customer again
    Customer sent too littleunderpaidSession stays underpaid; contact the customer. Create a new session for any remaining balance if needed.
    Customer sent too muchoverpaidSession transitions to paid; Crypto Checkout does not auto-refund. Handle the overage manually.
    Payment arrived just after expirypaid_lateWithin the late payment grace window (default: 10 minutes), the session is credited as paid_late. Treat as paid.

    Next steps

    Webhooks overview

    Learn about all available event types and delivery guarantees.

    Session lifecycle

    Understand every status and the transitions between them.

    Troubleshooting

    Fix common issues with payment detection and webhook delivery.