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.
1. Install the SDKs
npm install @paybytoken/node @paybytoken/checkoutSet the secret key only in the server environment:
PAYBYTOKEN_SECRET_KEY=sk_test_...
PAYBYTOKEN_CHECKOUT_ORIGIN=http://localhost:3000Do 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.
Did this page answer your question?
Your feedback helps us improve the integration path.