PaybytokenDocs
Checkout
Guide · Checkout

Embedded checkout quickstart

Mount Paybytoken stablecoin checkout securely inside a merchant website.

This guide uses Next.js, but the same server/browser split works with any backend and frontend.

Embedded integration boundary
Your backendCreate SessionSecret key + trusted order
Browser-safeClient secretBound to one origin
Your pageEmbedded checkoutWallet or manual transfer
Your backendVerified eventFulfill exactly once
The browser receives a short-lived client secret, never the merchant secret key.

1. Install the SDKs

npm install @paybytoken/node @paybytoken/checkout

Set the secret key only in the server environment:

PAYBYTOKEN_SECRET_KEY=sk_test_...
PAYBYTOKEN_CHECKOUT_ORIGIN=http://localhost:3000

Do not use a NEXT_PUBLIC_ prefix for the secret key.

2. Create a session on your server

Create a same-origin route such as app/api/paybytoken/checkout-session/route.ts:

import Paybytoken from '@paybytoken/node'

const paybytoken = new Paybytoken(process.env.PAYBYTOKEN_SECRET_KEY!)

export async function POST() {
  // Load names, quantities and prices from your database.
  const order = {
    id: 'order_1234',
    items: [
      {
        quantity: 1,
        unit_amount: '50.00',
        product_data: { name: 'Starter plan' },
      },
    ],
  }

  const session = await paybytoken.checkoutSessions.create({
    currency: 'usd',
    line_items: order.items,
    supported_tokens: [
      { currency: 'USDC', chain: 'base' },
      { currency: 'USDC', chain: 'ethereum' },
    ],
    ui_mode: 'embedded',
    allowed_origin: process.env.PAYBYTOKEN_CHECKOUT_ORIGIN!,
    metadata: { order_id: order.id },
  })

  return Response.json(
    {
      clientSecret: session.client_secret,
      checkoutUrl: session.url,
    },
    {
      status: 201,
      headers: { 'Cache-Control': 'no-store' },
    },
  )
}

The route must:

  • authenticate the customer when the order requires it;
  • resolve prices and quantities from merchant-owned server data;
  • use the exact origin of the page that will mount checkout;
  • disable shared and CDN caching; and
  • return only the session-scoped browser values.

The client_secret is returned only when the embedded session is created. Do not log it, persist it in browser storage, send it to analytics, or place it in a URL.

See Create a Checkout Session for every supported request field and the embedded response shape.

3. Mount checkout in the browser

import { initEmbeddedCheckout } from '@paybytoken/checkout'

const checkout = await initEmbeddedCheckout({
  fetchCheckoutSession: async () => {
    const response = await fetch('/api/paybytoken/checkout-session', {
      method: 'POST',
    })

    if (!response.ok) {
      throw new Error('Checkout session could not be created')
    }

    return response.json()
  },
})

checkout
  .on('ready', () => hideLoadingState())
  .on('complete', ({ data }) => showPaymentSubmitted(data))
  .on('cancel', () => restoreCart())
  .on('loaderror', ({ data }) => showRetry(data))
  .on('error', ({ data }) => showPaymentError(data))

checkout.mount('#paybytoken-checkout')

Add a stable, empty mount target:

<div id="paybytoken-checkout"></div>

Call unmount() when you temporarily hide checkout but want to preserve the session. Call destroy() before discarding the session, replacing the checkout instance or removing its event handlers.

4. Add Content Security Policy rules

connect-src 'self' https://api-prod.paybytoken.io;
frame-src https://checkout.paybytoken.io;

Use exact origins, not wildcard Paybytoken domains. If your configured merchant logo is hosted on another domain, allow that host in img-src.

5. Fulfill from your server

The browser complete event improves the customer experience, but it is not proof of payment. Fulfill only after verifying a signed payment_intent.succeeded webhook or retrieving the terminal state with your secret key. See Fulfill orders with webhooks.

On this page

API Workbench

Full Explorer

Open in new tab