Skip to main content

Overview

Lava uses a two-layer authentication system: secret keys for merchant identity and forward tokens for API requests. This guide shows you how to create and manage both, with working code examples for SDK and manual token generation.
Prerequisites: You need a Lava merchant account. If you haven’t signed up yet, start with the Build Quickstart.

Understanding Lava Authentication

Two-Layer System

Layer 1: Secret Keys (Your merchant credentials)
  • Created in your Lava dashboard
  • Prove you’re an authorized merchant
  • Never sent directly in API calls
  • Can be rotated for security
Layer 2: Forward Tokens (Composite request credentials)
  • Generated programmatically from secret key + connection + product
  • What you actually send in Authorization header
  • Each customer gets their own forward token
  • Scoped to specific wallet and pricing configuration

Why This Architecture?

This separation enables flexible billing. One secret key can generate many forward tokens—each connecting a different customer wallet to different pricing products. You maintain one credential, but serve many customers with custom pricing. ★ Insight ───────────────────────────────────── Authentication Architecture Pattern: The secret key → forward token relationship mirrors the OAuth pattern, where a client secret generates scoped access tokens. This architecture prevents credential leakage by never exposing the secret key in API requests, while enabling fine-grained access control per customer connection. ─────────────────────────────────────────────────

Creating Secret Keys

Your first secret key (“Default”) was created automatically on signup. To create additional keys:
  1. Open your dashboard at lavapayments.com/dashboard
  2. Navigate to Build > Secrets in the sidebar
  3. Click ”+ Create Secret Key”
  4. Enter a descriptive name (e.g., “Production API”, “Staging Environment”)
  5. Click Create
  6. Copy the key immediately - it’s only shown once
Secret keys are only displayed once at creation. Store them securely in your environment variables immediately. If you lose a key, delete it and create a new one.

When to Create Multiple Keys

Common scenarios:
  • Environment separation - Different keys for dev/staging/production
  • Security rotation - Create new key before revoking old one
  • Team separation - Different teams or projects with isolated credentials
  • Compliance - Meet SOC 2 or PCI requirements for credential rotation
Most developers only need the auto-generated “Default” key. Only create additional keys when you have a specific security or organizational need.

Generating Forward Tokens

The @lavapayments/nodejs SDK provides the generateForwardToken() method:
import { Lava } from '@lavapayments/nodejs';

const lava = new Lava(process.env.LAVA_SECRET_KEY!, {
  apiVersion: '2025-04-28.v1'
});

// After user completes checkout
const forwardToken = lava.generateForwardToken({
  connection_secret: 'conn_xyz789',  // From checkout webhook
  product_secret: 'prod_abc123'      // Your product ID (optional)
});

// Use in AI requests
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!' }]
  })
});
Parameters:
  • connection_secret - Received from checkout completion (identifies customer wallet)
  • product_secret - Your product configuration ID (optional, uses default pricing if omitted)

Manual Token Generation (Non-Node.js Environments)

For Python, Ruby, Go, or other languages, construct tokens manually: Forward token structure:
<secretKey>.<connectionSecret>.<productSecret>
Python example:
import base64
import json

def generate_forward_token(secret_key, connection_secret, product_secret=None):
    """Generate a Lava forward token manually"""
    token_data = {
        "secret_key": secret_key,
        "connection_secret": connection_secret,
        "product_secret": product_secret,
        "provider_key": None
    }

    # Encode as JSON then Base64
    json_str = json.dumps(token_data)
    base64_token = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')

    return base64_token

# Usage
forward_token = generate_forward_token(
    secret_key="aks_live_your_secret_key",
    connection_secret="conn_xyz789",
    product_secret="prod_abc123"
)

# Use in API requests
import requests

response = requests.post(
    'https://api.lavapayments.com/v1/forward?u=https://api.openai.com/v1/chat/completions',
    headers={
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {forward_token}'
    },
    json={
        'model': 'gpt-4o-mini',
        'messages': [{'role': 'user', 'content': 'Hello!'}]
    }
)
Ruby example:
require 'base64'
require 'json'
require 'net/http'

def generate_forward_token(secret_key, connection_secret, product_secret = nil)
  token_data = {
    secret_key: secret_key,
    connection_secret: connection_secret,
    product_secret: product_secret,
    provider_key: nil
  }

  Base64.strict_encode64(token_data.to_json)
end

# Usage
forward_token = generate_forward_token(
  'aks_live_your_secret_key',
  'conn_xyz789',
  'prod_abc123'
)

# Use in API requests
uri = URI('https://api.lavapayments.com/v1/forward?u=https://api.openai.com/v1/chat/completions')
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{forward_token}"
request.body = {
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Hello!' }]
}.to_json
Go example:
package main

import (
    "encoding/base64"
    "encoding/json"
)

type ForwardToken struct {
    SecretKey        string  `json:"secret_key"`
    ConnectionSecret string  `json:"connection_secret"`
    ProductSecret    *string `json:"product_secret"`
    ProviderKey      *string `json:"provider_key"`
}

func generateForwardToken(secretKey, connectionSecret, productSecret string) (string, error) {
    token := ForwardToken{
        SecretKey:        secretKey,
        ConnectionSecret: connectionSecret,
        ProductSecret:    &productSecret,
        ProviderKey:      nil,
    }

    jsonData, err := json.Marshal(token)
    if err != nil {
        return "", err
    }

    return base64.StdEncoding.EncodeToString(jsonData), nil
}

// Usage
forwardToken, err := generateForwardToken(
    "aks_live_your_secret_key",
    "conn_xyz789",
    "prod_abc123",
)
The token is a base64-encoded JSON object containing four fields: secret_key, connection_secret, product_secret, and provider_key. The structure is consistent across all languages.

Credential Rotation Strategy

When to Rotate Secret Keys

Immediate rotation required:
  • ✅ Key accidentally committed to public repository
  • ✅ Key shared via insecure channel (email, Slack)
  • ✅ Suspected unauthorized access
  • ✅ Employee with key access leaves company
Scheduled rotation recommended:
  • ✅ Every 90 days for compliance (SOC 2, PCI-DSS)
  • ✅ After major security incidents (even if unaffected)
  • ✅ When upgrading security policies

Zero-Downtime Key Rotation

Lava supports multiple active secret keys, enabling zero-downtime rotation: Step 1: Create New Key
# In Lava dashboard: Build > Secrets > Create Secret Key
# Name: "Production API v2" (or similar)
# Copy: aks_live_new_key_here
Step 2: Deploy New Key to Subset
# Rolling deployment to 10% of servers
export LAVA_SECRET_KEY=aks_live_new_key_here
Step 3: Monitor for Errors
# Check application logs for authentication errors
# Monitor Lava dashboard for 401 errors
# Verify requests are being billed correctly
Step 4: Complete Rollout
# Deploy new key to all servers
# Update environment variables across all environments
# Update CI/CD secrets
Step 5: Revoke Old Key
# In Lava dashboard: Build > Secrets > [Old Key] > Delete
# Only after confirming all systems use new key
Keep the old key active for 7 days after full deployment. This provides a safety window to rollback if issues arise.

Rotation Script Example

Automate key rotation with a deployment script:
#!/bin/bash
# rotate-lava-key.sh

set -e

echo "🔄 Starting Lava secret key rotation..."

# Verify new key is set
if [ -z "$NEW_LAVA_SECRET_KEY" ]; then
  echo "❌ Error: NEW_LAVA_SECRET_KEY environment variable not set"
  exit 1
fi

# Backup current key
OLD_KEY=$LAVA_SECRET_KEY
echo "📦 Backed up current key"

# Update environment variable
export LAVA_SECRET_KEY=$NEW_LAVA_SECRET_KEY
echo "✅ Updated LAVA_SECRET_KEY"

# Test new key with a simple request
echo "🧪 Testing new key..."
response=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $NEW_LAVA_SECRET_KEY" \
  https://api.lavapayments.com/v1/health)

if [ "$response" = "200" ]; then
  echo "✅ New key validated successfully"
else
  echo "❌ Key validation failed (HTTP $response)"
  export LAVA_SECRET_KEY=$OLD_KEY  # Rollback
  echo "↩️  Rolled back to previous key"
  exit 1
fi

# Update deployment
echo "🚀 Deploying with new key..."
# Your deployment commands here
# kubectl set env deployment/app LAVA_SECRET_KEY=$NEW_LAVA_SECRET_KEY
# vercel env add LAVA_SECRET_KEY $NEW_LAVA_SECRET_KEY

echo "✅ Key rotation complete"
echo "⚠️  Remember to delete old key in dashboard after 7 days"

Security Best Practices

1. Environment Variable Storage

Always store secret keys in environment variables, never hardcode:
// ✅ Correct - Use environment variables
const lava = new Lava(process.env.LAVA_SECRET_KEY!, {
  apiVersion: '2025-04-28.v1'
});

// ❌ Wrong - Hardcoded key
const lava = new Lava('aks_live_abc123def456', {
  apiVersion: '2025-04-28.v1'
});
Environment file setup:
# .env (add to .gitignore!)
LAVA_SECRET_KEY=aks_live_your_secret_key_here
LAVA_PRODUCT_SECRET=prod_your_product_secret_here
Never commit to git:
# .gitignore
.env
.env.local
.env.*.local

2. Separate Keys Per Environment

Use different secret keys for development, staging, and production:
# .env.development
LAVA_SECRET_KEY=aks_test_dev_key_here

# .env.staging
LAVA_SECRET_KEY=aks_test_staging_key_here

# .env.production
LAVA_SECRET_KEY=aks_live_production_key_here
Why this matters:
  • Development keys can be revoked without affecting production
  • Test mode keys prevent accidental real charges
  • Easier to track which environment generated requests

3. Backend-Only Token Generation

Never generate forward tokens in frontend code:
// ✅ Correct - Backend API endpoint
// Backend: /api/ai/chat
app.post('/api/ai/chat', async (req, res) => {
  const user = await auth.getUser(req);

  const token = lava.generateForwardToken({
    connection_secret: user.lavaConnectionSecret,
    product_secret: process.env.LAVA_PRODUCT_SECRET!
  });

  // Make AI request server-side
  const result = await makeAIRequest(token, req.body.message);
  res.json(result);
});

// ❌ Wrong - Frontend token exposure
// Backend: /api/get-token (DO NOT DO THIS)
app.get('/api/get-token', (req, res) => {
  const token = lava.generateForwardToken({ /* ... */ });
  res.json({ token }); // Token exposed to client!
});
Why: Exposing forward tokens in frontend code allows malicious users to make unlimited API requests against your wallet balance.

4. Connection Secret Storage

Store connection secrets securely in your database:
// Webhook handler stores connection secret
app.post('/webhooks/lava/checkout-complete', async (req, res) => {
  const { connection_id, reference_id } = req.body;

  // Retrieve full connection details
  const connection = await lava.connections.retrieve(connection_id);

  // Store securely in your database
  await db.users.update({
    where: { id: reference_id },
    data: { lavaConnectionSecret: connection.connection_secret }
  });

  res.json({ received: true });
});
Security checklist:
  • ✅ Store in encrypted database field
  • ✅ Never log connection secrets
  • ✅ Don’t expose in API responses
  • ✅ Implement access controls (only user’s own connection)

5. Secret Key Audit Trail

Track secret key usage for security compliance:
// Log key creation and deletion
import { Logger } from './logger';

class SecretKeyAudit {
  static log(action: 'created' | 'rotated' | 'deleted', keyName: string, actor: string) {
    Logger.security({
      event: 'secret_key_audit',
      action,
      key_name: keyName,
      actor,
      timestamp: new Date().toISOString()
    });
  }
}

// Usage
SecretKeyAudit.log('created', 'Production API v2', '[email protected]');
SecretKeyAudit.log('rotated', 'Production API', '[email protected]');
SecretKeyAudit.log('deleted', 'Old Production API', '[email protected]');

Troubleshooting

Cause: Invalid secret key or malformed forward tokenSolution:
  1. Verify secret key is correct: Check environment variables match dashboard
  2. Test key directly: Use the self forward token from dashboard (Build > Secrets > Test Your Setup)
  3. Check token format: Ensure using Bearer prefix in Authorization header
  4. Validate Base64 encoding: Decode token manually to verify structure
Debug command:
# Decode forward token to inspect contents
echo "your_forward_token" | base64 -d
# Should show JSON with secret_key, connection_secret, product_secret, provider_key
Cause: Connection secret doesn’t exist or was deletedSolution:
  1. Verify connection exists: Check dashboard under Monetize > Connections
  2. Check connection status: Ensure status is “active”, not “disabled” or “deleted”
  3. Validate reference ID: Confirm connection is linked to correct user in your database
  4. Recreate if needed: User can go through checkout flow again
Diagnostic query:
// Retrieve connection to check status
const connection = await lava.connections.retrieve(connectionId);
console.log('Connection status:', connection.status);
console.log('Connection deleted:', connection.deleted_at);
Cause: Product secret invalid or pricing not configured correctlySolution:
  1. Verify product exists: Dashboard > Monetize > Products
  2. Check product status: Ensure product is active
  3. Test without product secret: Omit product_secret to use default pricing
  4. Validate fee structure: Confirm product has fee configuration set
Test pattern:
// Test with product secret
const token1 = lava.generateForwardToken({
  connection_secret: 'conn_xyz',
  product_secret: 'prod_abc'  // Custom pricing
});

// Test without product secret (default pricing)
const token2 = lava.generateForwardToken({
  connection_secret: 'conn_xyz'
  // product_secret omitted - uses default
});
Cause: Old key deleted before all systems updated to new keySolution (Emergency rollback):
  1. Revert environment variables to previous key
  2. Restart application servers
  3. Create new key in dashboard (old key cannot be restored)
  4. Plan proper rotation with gradual rollout
Prevention checklist:
  • Create new key in dashboard
  • Test new key in non-production environment first
  • Deploy to production with rolling deployment (10% → 50% → 100%)
  • Monitor for 7 days before deleting old key
  • Keep rollback plan ready

Next Steps