Skip to main content
Lava checkout is a hosted payment flow you embed in your app. It handles phone verification, payment setup, and subscription creation in a single full-screen iframe overlay.
New to Lava? Start with the Charge Your First Customer quickstart for a step-by-step tutorial. This page is the detailed reference for checkout modes, integration patterns, and completion handling.

Checkout Modes

Lava checkout supports two modes, each for a different stage of the customer lifecycle.

Subscription Mode

Use when: A customer is starting or updating a recurring plan. This is the recommended mode for most use cases. What happens:
  1. Customer verifies phone number via SMS OTP (new customers enter their number; returning customers verify the phone on file)
  2. Customer adds payment method
  3. Customer is charged the plan amount (e.g., $25)
  4. Subscription is created with automatic monthly renewal
  5. You receive a customer_id to use for billing
Billing cycle:
  • Balance resets each cycle to the plan’s included credit
  • When credits run out, requests are blocked until credits are replenished (via auto top-up or manual bundle purchase)
  • Customer can cancel anytime
Use one subscription CTA in your app. For new customers, omit customer_id (checkout will collect identity). For returning customers, include customer_id to reuse their existing customer record.

Credit Bundle Mode

Use when: An existing subscriber wants to buy a fixed credit pack attached to their plan. What happens:
  1. Customer verifies identity via SMS OTP (sent to the phone on file)
  2. Customer sees the bundle (name, price, credit amount) and confirms payment
  3. Credits are added to their current subscription cycle
Bundle IDs are available in the dashboard when you manage plans (each plan’s credit bundles include the ID).

Which Mode Should I Use?


Backend: Create a Session

Before opening checkout, create a checkout session on your backend. The session token authenticates the checkout flow. origin_url must match the domain that opens the checkout iframe — Lava uses it to restrict which origins can embed the flow.
The parameters vary by mode:
Checkout sessions expire after 60 minutes. Create a new session for each checkout attempt. Never reuse tokens across multiple users.

Frontend: Embed Checkout

The @lavapayments/checkout package exports a useLavaCheckout hook that opens a full-screen checkout iframe when you call open() with a session token.
open() renders a full-screen iframe overlay. The checkout flow happens inside the iframe. When the user completes or cancels, Lava posts a message back to your page, triggering the appropriate callback.

Handling Completion

When checkout completes, you receive a customer_id — the billing relationship between this customer and your merchant account. This is the ID you’ll use for all future billing operations: generating forward tokens, checking balance, and retrieving usage.
Store the customer_id in your database alongside your internal user ID. This lets you look up a customer directly without iterating through lists.
Use frontend callbacks for immediate UX and backend webhooks for reliable processing. In production, use both.

Frontend Callbacks

The onSuccess callback fires immediately when checkout completes — use it for instant UI feedback like toasts and redirects. Pros: Immediate feedback, no setup required, great for development. Cons: User can close browser before callback executes. Not reliable enough for production alone. Configure a webhook to receive customer.created events for reliable server-side processing. See the Webhooks guide for full setup instructions.

Combined Pattern (Best Practice)

Use callbacks for instant UX, webhooks for backend finalization:
Always verify webhook signatures. Without verification, malicious actors could send fake events to your endpoint. See Webhook Signature Verification.

Troubleshooting

Cause: Checkout session was created more than 60 minutes ago.Solution: Create a new session when the user clicks the checkout button. Don’t pre-create sessions on page load.
Cause: Invalid or missing checkout session token.Solution:
  1. Verify @lavapayments/checkout is installed
  2. Ensure the component uses the 'use client' directive (Next.js App Router)
  3. Check that open() is called with the checkout_session_token from your backend
  4. Check browser console for errors
Cause: Invalid phone number format or OTP delivery issues.Solution:
  • Phone numbers must be in E.164 format: +15551234567
  • Verify the phone number can receive SMS
  • Common mistakes: (555) 123-4567 (formatted) and 555-123-4567 (missing country code) will not work
Cause: Incorrect webhook URL, firewall blocking, or signature verification failing.Solution:
  1. Verify webhook URL is publicly accessible
  2. Check webhook logs in the Lava dashboard
  3. Verify signature validation uses X-Webhook-Signature header with HMAC SHA-256
  4. Ensure your endpoint returns a 200 status code

Next Steps

Forward Proxy

Generate forward tokens to bill customers on each request

Webhooks

Receive notifications for customer and balance events