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
Dashboard Method (Recommended)
Your first secret key (“Default”) was created automatically on signup. To create additional keys:
Open your dashboard at lavapayments.com/dashboard
Navigate to Build > Secrets in the sidebar
Click ”+ Create Secret Key”
Enter a descriptive name (e.g., “Production API”, “Staging Environment”)
Click Create
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
SDK Method (Recommended for Node.js)
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:
Verify secret key is correct: Check environment variables match dashboard
Test key directly: Use the self forward token from dashboard (Build > Secrets > Test Your Setup)
Check token format: Ensure using Bearer prefix in Authorization header
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
Connection secret not found
Cause: Connection secret doesn’t exist or was deletedSolution:
Verify connection exists: Check dashboard under Monetize > Connections
Check connection status: Ensure status is “active”, not “disabled” or “deleted”
Validate reference ID: Confirm connection is linked to correct user in your database
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 );
Product secret not applying pricing
Cause: Product secret invalid or pricing not configured correctlySolution:
Verify product exists: Dashboard > Monetize > Products
Check product status: Ensure product is active
Test without product secret: Omit product_secret to use default pricing
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
});
Key rotation caused service disruption
Cause: Old key deleted before all systems updated to new keySolution (Emergency rollback):
Revert environment variables to previous key
Restart application servers
Create new key in dashboard (old key cannot be restored)
Plan proper rotation with gradual rollout
Prevention checklist:
Next Steps