Checkout Modes
Lava checkout supports two modes, each for a different stage of the customer lifecycle.| Mode | Purpose | Creates Customer? | Requires Customer? |
|---|---|---|---|
subscription | Subscribe to a recurring plan | Yes | No |
credit_bundle | Buy a fixed credit pack (subscribers only) | No | Yes |
Subscription Mode
Use when: A customer is starting or updating a recurring plan. This is the recommended mode for most use cases. What happens:- Customer verifies phone number via SMS OTP (new customers enter their number; returning customers verify the phone on file)
- Customer adds payment method
- Customer is charged the plan amount (e.g., $25)
- Subscription is created with automatic monthly renewal
- You receive a
customer_idto use for billing
- 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
Credit Bundle Mode
Use when: An existing subscriber wants to buy a fixed credit pack attached to their plan. What happens:- Customer verifies identity via SMS OTP (sent to the phone on file)
- Customer sees the bundle (name, price, credit amount) and confirms payment
- Credits are added to their current subscription cycle
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.
| Parameter | subscription | credit_bundle |
|---|---|---|
origin_url | required | required |
plan_id | required | — |
customer_id | optional | required |
credit_bundle_id | — | required |
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 acustomer_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.
Use frontend callbacks for immediate UX and backend webhooks for reliable processing. In production, use both.
Frontend Callbacks
TheonSuccess 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.
Backend Webhooks (Recommended)
Configure a webhook to receivecustomer.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:Troubleshooting
Session expired error
Session expired error
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.
Checkout not opening
Checkout not opening
Cause: Invalid or missing checkout session token.Solution:
- Verify
@lavapayments/checkoutis installed - Ensure the component uses the
'use client'directive (Next.js App Router) - Check that
open()is called with thecheckout_session_tokenfrom your backend - Check browser console for errors
Phone verification failing
Phone verification failing
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) and555-123-4567(missing country code) will not work
Webhook not receiving events
Webhook not receiving events
Cause: Incorrect webhook URL, firewall blocking, or signature verification failing.Solution:
- Verify webhook URL is publicly accessible
- Check webhook logs in the Lava dashboard
- Verify signature validation uses
X-Webhook-Signatureheader with HMAC SHA-256 - Ensure your endpoint returns a 200 status code