Skip to main content

What You’ll Build

In this quickstart, you’ll set up a complete monetization flow for your AI service. By the end, you’ll have:
  • A Stripe Connect account configured for payouts
  • A product with custom pricing rules
  • A functioning checkout flow to onboard customers
  • Customer wallet connections for billing
  • Forward tokens generated for API authentication
  • Revenue tracking visible in your dashboard
Prerequisites: You need a Lava merchant account and have completed the Build Quickstart. This tutorial builds on that foundation to add customer billing.

Step 1: Connect Your Stripe Account

Before you can earn revenue, Lava needs a way to pay you. We use Stripe Connect Express for secure, automated payouts.

Start Stripe Onboarding

  1. Open your dashboard at lavapayments.com/dashboard
  2. Navigate to Monetize > Billing in the sidebar
  3. Click “Connect Stripe Account”
  4. Complete the Stripe onboarding form:
    • Business details (name, type, address)
    • Bank account for payouts
    • Tax information (EIN or SSN)
  5. Submit and wait for approval (usually instant)
Already have a Stripe account? You can link your existing Stripe account during onboarding. Lava creates a Connect Express account that keeps your revenue separate from other Stripe activity.

Verify Connection

After completing onboarding, you’ll see:
  • Payout Schedule: When you’ll receive funds (typically daily or weekly)
  • Account Status: “Active” when ready to accept payments
  • Balance: Current pending payout amount
Stripe handles all compliance, tax reporting, and payment processing. Lava never holds your funds—payouts go directly to your bank account on the schedule you configure.

Step 2: Create Your First Product

Products define how you charge customers. Let’s create a product with tiered pricing to reward high-volume usage.
  1. Navigate to Monetize > Products
  2. Click “New Product”
  3. Configure your pricing:
Basic Settings:
  • Name: “AI Chat API - Standard”
  • Description: “Production API access with tiered pricing”
Fee Structure: Choose your pricing model:
  • Fixed Fee: $0.05 per 1K tokens (predictable pricing)
  • Percentage Markup: Leave at 0% for now
  • Billing Basis: “Input + Output Tokens”
Tiered Pricing (optional but recommended):
  • Tier 1: 0-1M tokens at $0.05 per 1K tokens
  • Tier 2: 1M-10M tokens at $0.03 per 1K tokens
  • Tier 3: 10M+ tokens at $0.01 per 1K tokens
Cost Attribution:
  • Base Costs: “Wallet Pays” (users cover provider costs)
  • Merchant Fees: “Wallet Pays” (users cover your markup)
Overdraft Behavior:
  • When balance is low: “Block Requests” (safe default)
  1. Click “Create Product”
  2. Copy the Product Secret (looks like prod_xxxxxxxxxxxxx)

Via API (For Programmatic Setup)

import { Lava } from '@lavapayments/nodejs';

const lava = new Lava({
  secretKey: process.env.LAVA_SECRET_KEY!
});

const product = await lava.products.create({
  name: 'AI Chat API - Standard',
  description: 'Production API access with tiered pricing',
  feeStructure: {
    fixedFee: 0.05,       // $0.05 per 1K tokens
    percentageFee: 0,     // No percentage markup
    billingBasis: 'input-output',  // Charge for both prompt + completion
  },
  tiers: [
    { upTo: 1000000, fixedFee: 0.05, percentageFee: 0 },
    { upTo: 10000000, fixedFee: 0.03, percentageFee: 0 },
    { upTo: null, fixedFee: 0.01, percentageFee: 0 }  // null = unlimited
  ],
  costAttribution: {
    baseCostsPaidBy: 'wallet',
    merchantFeesPaidBy: 'wallet'
  },
  overdraftAllowed: false
});

console.log('Product created:', product.productSecret);
What’s a product secret? It’s a unique identifier that gets included in forward tokens to apply specific pricing rules. You can create multiple products for different pricing tiers (e.g., “Hobby”, “Pro”, “Enterprise”).

Step 3: Install the Checkout Component

Lava provides a React component that handles wallet creation, phone verification, and payment—all in one embedded flow.

Installation

npm install @lavapayments/checkout

Component Overview

The <LavaCheckout> component provides:
  • Phone verification via SMS OTP (Twilio)
  • Wallet creation for new users OR linking existing wallets
  • Stripe payment integration for adding funds
  • Connection creation linking the wallet to your merchant account
  • Success callback with connection credentials for API calls
The checkout component is fully customizable with your branding, custom amounts, and redirect URLs. See the Checkout Integration Guide for advanced configuration.

Step 4: Create a Checkout Session (Backend)

Before embedding the checkout component, you need to create a session on your backend. This ensures security and lets you configure checkout behavior.

Next.js API Route Example

// pages/api/checkout/create-session.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Lava } from '@lavapayments/nodejs';

const lava = new Lava({
  secretKey: process.env.LAVA_SECRET_KEY!
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { amount, productId } = req.body;

  try {
    const session = await lava.checkout.createSession({
      mode: 'subscription',  // or 'topup' for one-time payment
      productId: productId,  // Your product secret from Step 2
      amount: amount,        // In cents (e.g., 5000 = $50.00)
      successUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard?checkout=success`,
      cancelUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/pricing?checkout=cancelled`
    });

    res.status(200).json({
      sessionSecret: session.sessionSecret,
      sessionId: session.sessionId
    });
  } catch (error) {
    console.error('Checkout session creation failed:', error);
    res.status(500).json({ error: 'Failed to create checkout session' });
  }
}

Express.js Example

import express from 'express';
import { Lava } from '@lavapayments/nodejs';

const app = express();
const lava = new Lava({ secretKey: process.env.LAVA_SECRET_KEY! });

app.post('/api/checkout/create-session', async (req, res) => {
  const { amount, productId } = req.body;

  try {
    const session = await lava.checkout.createSession({
      mode: 'subscription',
      productId: productId,
      amount: amount,
      successUrl: `${process.env.BASE_URL}/dashboard?checkout=success`,
      cancelUrl: `${process.env.BASE_URL}/pricing?checkout=cancelled`
    });

    res.json({
      sessionSecret: session.sessionSecret,
      sessionId: session.sessionId
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to create checkout session' });
  }
});
Session secrets are single-use and expire after 30 minutes. Create a new session for each checkout attempt. Sessions are stored in Redis for fast access during the checkout flow.

Step 5: Embed the Checkout Component (Frontend)

Now embed the checkout component in your React application.

Basic Integration

'use client';  // Next.js App Router

import { LavaCheckout } from '@lavapayments/checkout';
import { useState } from 'react';

export function PricingPage() {
  const [checkoutSession, setCheckoutSession] = useState<string | null>(null);

  async function handleCheckoutClick() {
    // Call your backend to create a session
    const response = await fetch('/api/checkout/create-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        amount: 5000,  // $50.00 in cents
        productId: 'prod_your_product_secret'
      })
    });

    const { sessionSecret } = await response.json();
    setCheckoutSession(sessionSecret);
  }

  return (
    <div>
      {!checkoutSession ? (
        <button onClick={handleCheckoutClick}>
          Start Free Trial - $50 Credit
        </button>
      ) : (
        <LavaCheckout
          sessionSecret={checkoutSession}
          onSuccess={(connection) => {
            console.log('Checkout completed!', connection);
            // Redirect to dashboard or show success message
            window.location.href = '/dashboard?checkout=success';
          }}
          onError={(error) => {
            console.error('Checkout failed:', error);
            alert('Checkout failed. Please try again.');
          }}
        />
      )}
    </div>
  );
}

With Custom Styling

<LavaCheckout
  sessionSecret={checkoutSession}
  onSuccess={(connection) => {
    // Handle success
  }}
  theme={{
    primaryColor: '#4f000b',     // Lava brand color
    borderRadius: '8px',
    fontFamily: 'Inter, sans-serif'
  }}
  className="max-w-md mx-auto"
/>
Testing the checkout flow? Use Stripe test mode with test card 4242 4242 4242 4242. The checkout component automatically detects your environment and uses test mode when appropriate.

Step 6: Handle Checkout Completion

When a user completes checkout, you need to capture the connection details to generate forward tokens for API authentication.

Frontend Callback (Immediate)

The onSuccess callback receives connection data immediately after payment:
onSuccess={(connection) => {
  // Connection object contains:
  // - connectionId: Unique identifier
  // - connectionSecret: Used in forward tokens
  // - walletId: User's wallet identifier
  // - status: 'ok' or 'limited'

  // Store in your database for the user
  await fetch('/api/users/save-connection', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      userId: currentUser.id,
      connectionId: connection.connectionId,
      connectionSecret: connection.connectionSecret
    })
  });

  // Redirect to success page
  window.location.href = '/dashboard?checkout=success';
}}

Webhook Handler (Reliable Backup)

Set up a webhook handler to capture checkout events reliably (handles network failures, closed tabs, etc.):
// pages/api/webhooks/lava.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Lava } from '@lavapayments/nodejs';

const lava = new Lava({ secretKey: process.env.LAVA_SECRET_KEY! });

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const signature = req.headers['x-webhook-signature'] as string;
  const payload = JSON.stringify(req.body);

  // Verify webhook signature (IMPORTANT for security)
  const isValid = lava.webhooks.verify(payload, signature, {
    secret: process.env.LAVA_WEBHOOK_SECRET!
  });

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = req.body;

  // Handle different event types
  switch (event.type) {
    case 'checkout.completed':
      // Save connection to your database
      await saveConnection({
        userId: event.data.metadata.userId,  // Set in Step 4
        connectionId: event.data.connectionId,
        connectionSecret: event.data.connectionSecret,
        status: event.data.status
      });
      break;

    case 'connection.wallet.balance.updated':
      // Handle balance changes (optional)
      console.log('Balance updated:', event.data.balance);
      break;

    case 'connection.deleted':
      // Handle connection deletion
      await deleteConnection(event.data.connectionId);
      break;
  }

  res.status(200).json({ received: true });
}

// Helper function
async function saveConnection(data: any) {
  // Save to your database (Prisma, Drizzle, etc.)
  // await db.connections.create({ data });
}
Always verify webhook signatures! This prevents attackers from sending fake webhook events to your endpoint. The lava.webhooks.verify() method uses HMAC SHA-256 for secure verification.

Webhook Configuration

  1. Navigate to Monetize > Webhooks in the dashboard
  2. Click “Create Webhook”
  3. Enter your webhook URL: https://yourdomain.com/api/webhooks/lava
  4. Select events: checkout.completed, connection.wallet.balance.updated
  5. Copy the webhook secret and add to your .env:
LAVA_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Step 7: Generate Forward Tokens

Now that you have a connection, generate forward tokens for your customers to use in API calls.
import { Lava } from '@lavapayments/nodejs';

const lava = new Lava({
  secretKey: process.env.LAVA_SECRET_KEY!
});

// Generate a forward token for a customer
const forwardToken = lava.auth.generateForwardToken({
  connectionSecret: connection.connectionSecret,  // From Step 6
  productSecret: 'prod_your_product_secret'       // From Step 2 (optional)
});

// Return to customer (store in their account or send via API)
return {
  forwardToken: forwardToken,
  instructions: 'Use this token in the Authorization header for AI requests'
};

Manual Generation (Advanced)

If you’re not using Node.js, you can generate tokens manually:
# Python example
import base64
import json

def generate_forward_token(secret_key, connection_secret, product_secret=None):
    parts = [secret_key, connection_secret]
    if product_secret:
        parts.append(product_secret)

    token_string = '.'.join(parts)
    encoded = base64.b64encode(token_string.encode()).decode()
    return encoded

# Usage
token = generate_forward_token(
    secret_key='sk_live_xxxxx',
    connection_secret='conn_sec_xxxxx',
    product_secret='prod_xxxxx'  # Optional
)

Distributing Tokens to Customers

Once generated, customers use forward tokens in their API calls:
const response = await fetch('https://api.lavapayments.com/v1/forward?u=https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${forwardToken}`
  },
  body: JSON.stringify({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: 'Hello!' }]
  })
});
Security best practice: Never expose your secret key to customers. Only distribute forward tokens, which are scoped to specific connections and products. Customers cannot access other connections or modify pricing with a forward token.

Step 8: Track Revenue in Your Dashboard

Monitor your revenue, usage, and customer activity in real-time.

Revenue Dashboard

Navigate to Monetize > Revenue to see: Overview Metrics:
  • Total Revenue: Lifetime earnings from all customers
  • Active Connections: Number of customers currently using your service
  • This Month’s Revenue: Current billing period earnings
  • Average Revenue Per User (ARPU)
Revenue Breakdown:
  • By Connection: See which customers generate the most revenue
  • By Product: Compare performance of different pricing tiers
  • By Time Period: Daily, weekly, monthly trends

Usage Analytics

Navigate to Monetize > Usage for detailed insights: Metrics Available:
  • Request Volume: Total API calls per connection
  • Token Usage: Input/output token consumption
  • Cost Breakdown: Base costs, fees, service charges
  • Model Distribution: Which AI models customers use most
Filtering Options:
  • Filter by date range, connection, product, or metadata
  • Export to CSV for external analysis
  • Create custom views for reporting

Connection Management

Navigate to Monetize > Connections to:
  • View All Connections: See every customer wallet linked to your merchant account
  • Connection Status: “ok” (funded), “limited” (low balance), “disabled” (blocked)
  • Balance Information: Current wallet balance per connection
  • Usage History: Request logs and costs for each connection
  • Reference IDs: Track customers using your internal user IDs
Set custom reference IDs in the checkout session creation to link Lava connections to your existing user system. This makes it easy to match revenue to your customer records.

Validation Checklist

Before going to production, verify everything works:
  • Stripe Connected: Payout account active and verified
  • Product Created: Pricing configured and product secret saved
  • Checkout Works: Test user can complete phone verification and payment
  • Webhook Configured: Endpoint receiving checkout.completed events
  • Forward Tokens Generated: SDK correctly creates tokens from connection secrets
  • API Requests Working: Test request with forward token succeeds
  • Revenue Tracking: Dashboard shows usage and costs accurately
  • Balance Deduction: Wallet balance decreases after requests
Test thoroughly before production! Use Stripe test mode and create test connections to verify the complete flow. Check that webhook signatures are validated correctly to prevent security vulnerabilities.

Troubleshooting

Possible causes:
  • Incomplete business information
  • Bank account verification failed
  • Tax ID (EIN/SSN) mismatch
Solution:
  1. Check your email for Stripe verification requests
  2. Complete any pending requirements in the Stripe dashboard
  3. Verify your bank account details are correct
  4. Contact Stripe support if issues persist
Common issue: If you already have a Stripe account, make sure you’re linking it correctly during onboarding (not creating a duplicate).
Possible causes:
  • Session secret expired (30-minute limit)
  • Invalid session secret format
  • Missing @lavapayments/checkout package
Solution:
  1. Verify the package is installed: npm list @lavapayments/checkout
  2. Check that sessionSecret is valid and not expired
  3. Create a new session if it’s been longer than 30 minutes
  4. Inspect browser console for errors
Debug checklist:
  • Session secret starts with cs_ prefix
  • Backend API returns valid JSON with sessionSecret field
  • React component is mounted in a client component ('use client' directive)
Possible causes:
  • Incorrect webhook URL configuration
  • Firewall blocking Lava’s servers
  • Webhook secret mismatch in signature verification
Solution:
  1. Verify URL is publicly accessible (use webhook.site to test)
  2. Check webhook logs in Monetize > Webhooks dashboard
  3. Ensure LAVA_WEBHOOK_SECRET matches the secret in dashboard
  4. Test locally using ngrok or similar tunneling tool
Testing locally:
# Use ngrok to expose localhost
ngrok http 3000

# Update webhook URL to ngrok HTTPS URL
https://abc123.ngrok.io/api/webhooks/lava
Retry logic: Lava automatically retries failed webhooks with exponential backoff (up to 3 attempts). Check the dashboard for retry logs.
Possible causes:
  • Incorrectly generated token (wrong format)
  • Expired or revoked secret key
  • Connection secret invalid or deleted
Solution:
  1. Verify token format: base64(secretKey.connectionSecret.productSecret)
  2. Check that secret key is active in Build > Secrets
  3. Verify connection exists in Monetize > Connections
  4. Regenerate token using SDK to ensure correct encoding
Test token manually:
// Decode to verify contents
const decoded = Buffer.from(forwardToken, 'base64').toString();
console.log(decoded);  // Should show: sk_xxx.conn_sec_xxx.prod_xxx
Common mistake: Forgetting to include the product secret when you have multiple products configured.
Possible causes:
  • Stripe payment succeeded but webhook not processed
  • Database race condition with concurrent requests
  • Balance transfer settlement delay
Solution:
  1. Check Stripe dashboard for successful payment
  2. Verify checkout.completed webhook was received and processed
  3. Look for errors in webhook handler logs
  4. Check balance manually in Monetize > Connections
Manual balance check: Navigate to the connection detail page and refresh. If Stripe shows payment success but Lava doesn’t, check for webhook processing errors in your server logs.Autopay alternative: Enable autopay to automatically top up when balance is low, preventing service interruption.
Possible causes:
  • Product secret not included in forward token
  • Tiered pricing configuration incorrect
  • Billing basis mismatch (input-output vs output-only)
Solution:
  1. Verify forward token includes product secret as third component
  2. Check product configuration in dashboard (Monetize > Products)
  3. Review request logs for correct usage calculation
  4. Test with simple fixed fee before adding tiers
Debugging pricing:
  • Check dashboard request logs to see calculated cost
  • Compare against expected calculation: tokens × tier_rate
  • Verify billingBasis matches your expectations (input+output vs output-only)
Example issue: Billing basis set to output-only but expecting to charge for prompts. Change to input-output to charge for both.

What’s Next?

Congratulations! You’ve set up a complete monetization flow. Here are recommended next steps:

How Lava Monetize Works

Understanding the monetization flow helps you troubleshoot and optimize:
  1. Customer Checkout: User verifies phone, adds payment, creates wallet
  2. Connection Created: Wallet is linked to your merchant account
  3. Forward Token Generated: You create token from connection + product secrets
  4. API Requests: Customer makes AI requests with forward token
  5. Usage Metered: Lava tracks tokens, calculates costs based on your product pricing
  6. Balance Deducted: Customer wallet charged immediately (prepaid model)
  7. Revenue Tracked: Your merchant earnings accumulate in real-time
  8. Payouts: Stripe transfers funds to your bank on configured schedule
Key Concepts: Transfers Ledger Every request generates transfer records:
  • Base Cost: AI provider’s charge (e.g., OpenAI)
  • Merchant Fee: Your configured markup
  • Service Charge: Lava’s 1.9% platform fee
Settlement Transfers are immediately “settled” if wallet has funds. If insufficient:
  • Transfer created as “under-settled”
  • When customer adds funds, oldest transfers settled first
  • Merchant earnings only count settled amounts
Balance Management
  • Active Balance: Current spendable amount
  • Autopay: Auto-top-up when balance falls below threshold
  • Connection Status: “ok” (funded), “limited” (low balance), “disabled” (blocked)
Lava adds less than 20ms of latency through edge computing and Redis caching. Streaming responses work seamlessly, with usage tracked after completion.

Support

Need help with monetization setup? We’re here to assist: