Overview
The Subscriptions resource handles recurring billing for your customers. It consists of two related concepts:
- Subscription Configurations - Your plan templates (e.g., “Basic 10/month","Premium25/month”)
- Active Subscriptions - Customer enrollments in those plans
Create subscription configurations once, then customers can subscribe through checkout sessions.
Subscription Configurations
listConfigs()
List all subscription plan templates for your merchant account.
Signature
listConfigs(): Promise<RestSubscriptionConfig[]>
Returns
Array<{
subscription_config_id: string;
merchant_id: string;
name: string; // Plan name (e.g., "Premium Plan")
period_amount: string; // USD amount per billing period
rollover_type: 'full' | 'none'; // Whether unused credits roll over
created_at: string;
}>
Example
const configs = await lava.subscriptions.listConfigs();
for (const config of configs) {
console.log(`${config.name}: $${config.period_amount}/month`);
console.log(`Rollover: ${config.rollover_type}`);
}
createConfig()
Create a new subscription plan template.
Signature
createConfig(params: CreateSubscriptionConfigParams): Promise<RestSubscriptionConfig>
Parameters
| Name | Type | Required | Description |
|---|
name | string | Yes | Display name for the plan |
period_amount | string | Yes | USD amount per billing period (decimal string) |
rollover_type | 'full' | 'none' | Yes | Whether unused credits carry over to next period |
Returns
Single RestSubscriptionConfig object.
Example: Basic Plan
const basicPlan = await lava.subscriptions.createConfig({
name: 'Basic Plan',
period_amount: '10.00',
rollover_type: 'none'
});
console.log('Created plan:', basicPlan.subscription_config_id);
Example: Premium Plan with Rollover
const premiumPlan = await lava.subscriptions.createConfig({
name: 'Premium Plan',
period_amount: '25.00',
rollover_type: 'full' // Unused credits roll over
});
console.log('Created plan:', premiumPlan.subscription_config_id);
updateConfig()
Update an existing subscription plan.
Signature
updateConfig(
id: string,
params: UpdateSubscriptionConfigParams
): Promise<RestSubscriptionConfig>
Parameters
| Name | Type | Required | Description |
|---|
id | string | Yes | Subscription config ID to update |
name | string | No | New display name |
period_amount | string | No | New monthly amount |
rollover_type | 'full' | 'none' | No | New rollover policy |
Returns
Updated RestSubscriptionConfig object.
Example
const updated = await lava.subscriptions.updateConfig('subconf_abc123', {
period_amount: '15.00' // Price increase
});
console.log('Updated plan:', updated.name);
Updating a subscription config affects all future billing cycles for existing subscribers. Past billing cycles are not affected.
deleteConfig()
Delete a subscription plan template.
Signature
deleteConfig(id: string): Promise<{ success: boolean }>
Parameters
| Name | Type | Required | Description |
|---|
id | string | Yes | Subscription config ID to delete |
Returns
Example
const result = await lava.subscriptions.deleteConfig('subconf_old_plan');
if (result.success) {
console.log('Plan deleted successfully');
}
You cannot delete a subscription config that has active subscribers. Cancel all active subscriptions first.
Active Subscriptions
listActiveSubscriptions()
List all customers currently subscribed to your plans.
Signature
listActiveSubscriptions(): Promise<RestActiveSubscription[]>
Returns
Array<{
active_subscription_id: string;
wallet_id: string;
subscription_config_id: string;
started_at: string; // ISO 8601 timestamp
billing_anchor: number; // Day of month (1-31) for billing
cancelled_at?: string;
status: 'active' | 'cancelled';
created_at: string;
subscription_config: RestSubscriptionConfig; // Nested plan details
wallet: {
wallet_id: string;
phone: string;
email?: string;
first_name?: string;
last_name?: string;
active_balance: string;
created_at: string;
};
}>
Example: List All Subscribers
const subscriptions = await lava.subscriptions.listActiveSubscriptions();
console.log(`Total subscribers: ${subscriptions.length}`);
for (const sub of subscriptions) {
console.log(`${sub.wallet.email}: ${sub.subscription_config.name}`);
console.log(` Billing on day ${sub.billing_anchor} of each month`);
console.log(` Status: ${sub.status}`);
}
Example: Filter by Plan
const subs = await lava.subscriptions.listActiveSubscriptions();
const premiumSubs = subs.filter(
sub => sub.subscription_config.name === 'Premium Plan'
);
console.log(`Premium subscribers: ${premiumSubs.length}`);
Example: Find Upcoming Renewals
const subs = await lava.subscriptions.listActiveSubscriptions();
const today = new Date().getDate();
const upcomingRenewals = subs.filter(sub =>
sub.billing_anchor >= today && sub.billing_anchor <= today + 7
);
console.log(`Renewals in next 7 days: ${upcomingRenewals.length}`);
cancelActiveSubscription()
Cancel a customer’s subscription. They will not be charged again.
Signature
cancelActiveSubscription(id: string): Promise<{ success: boolean }>
Parameters
| Name | Type | Required | Description |
|---|
id | string | Yes | Active subscription ID to cancel |
Returns
Example
const result = await lava.subscriptions.cancelActiveSubscription('sub_abc123');
if (result.success) {
console.log('Subscription cancelled');
// Customer will not be charged again
// Existing balance is preserved
}
Cancelling a subscription does not refund the current period. The customer retains their wallet balance and can continue using credits until depleted.
Common Use Cases
Setting Up Subscription Tiers
Create multiple plan levels:
async function setupSubscriptionTiers() {
const tiers = [
{ name: 'Starter', amount: '5.00', rollover: 'none' },
{ name: 'Pro', amount: '15.00', rollover: 'full' },
{ name: 'Enterprise', amount: '50.00', rollover: 'full' }
];
const configs = await Promise.all(
tiers.map(tier =>
lava.subscriptions.createConfig({
name: tier.name,
period_amount: tier.amount,
rollover_type: tier.rollover as 'full' | 'none'
})
)
);
return configs.map(config => ({
id: config.subscription_config_id,
name: config.name
}));
}
// Store config IDs in environment variables
const tiers = await setupSubscriptionTiers();
console.log('STARTER_PLAN_ID=' + tiers[0].id);
console.log('PRO_PLAN_ID=' + tiers[1].id);
console.log('ENTERPRISE_PLAN_ID=' + tiers[2].id);
Customer Subscription Flow
Integrate subscriptions with checkout:
// 1. Customer selects a plan on your frontend
app.post('/api/subscribe/:tier', async (req, res) => {
const tier = req.params.tier; // 'starter', 'pro', 'enterprise'
// Map tier to subscription config ID
const configId = {
starter: process.env.STARTER_PLAN_ID,
pro: process.env.PRO_PLAN_ID,
enterprise: process.env.ENTERPRISE_PLAN_ID
}[tier];
if (!configId) {
return res.status(400).json({ error: 'Invalid tier' });
}
// 2. Create subscription checkout session
const session = await lava.checkoutSessions.create({
checkout_mode: 'subscription',
origin_url: 'https://yourapp.com',
subscription_config_id: configId,
reference_id: req.user.id
});
// 3. Return checkout token to frontend
res.json({
checkoutToken: session.checkout_session_token
});
});
// 4. After checkout completes, subscription is automatically created
// Listen for webhook: subscription.created
app.post('/webhooks/lava/subscription-created', async (req, res) => {
const { active_subscription_id, wallet_id, subscription_config_id } = req.body;
console.log('New subscription:', active_subscription_id);
// Update your database
await database.subscriptions.create({
userId: req.body.reference_id,
lavaSubscriptionId: active_subscription_id,
tier: getTierFromConfigId(subscription_config_id)
});
res.json({ received: true });
});
Subscription Management Dashboard
Display subscriber analytics:
async function getSubscriptionMetrics() {
const subs = await lava.subscriptions.listActiveSubscriptions();
const byPlan = subs.reduce((acc, sub) => {
const planName = sub.subscription_config.name;
if (!acc[planName]) {
acc[planName] = {
count: 0,
mrr: 0, // Monthly Recurring Revenue
customers: []
};
}
acc[planName].count++;
acc[planName].mrr += parseFloat(sub.subscription_config.period_amount);
acc[planName].customers.push({
email: sub.wallet.email,
startedAt: sub.started_at
});
return acc;
}, {} as Record<string, any>);
const totalMRR = Object.values(byPlan).reduce(
(sum: number, plan: any) => sum + plan.mrr,
0
);
return {
totalSubscribers: subs.length,
totalMRR,
byPlan,
activeCount: subs.filter(s => s.status === 'active').length,
cancelledCount: subs.filter(s => s.status === 'cancelled').length
};
}
// Usage
const metrics = await getSubscriptionMetrics();
console.log('Total MRR:', metrics.totalMRR);
console.log('Subscribers by plan:', metrics.byPlan);
Automatic Cancellation for Unpaid Invoices
Cancel subscriptions with failed payments:
// Webhook handler for failed subscription charge
app.post('/webhooks/lava/subscription-charge-failed', async (req, res) => {
const { active_subscription_id, wallet_id } = req.body;
console.log('Subscription charge failed:', active_subscription_id);
// Give customer 3 days to update payment method
setTimeout(async () => {
// Check if payment was made
const wallet = await lava.connections.list({ /* filter by wallet_id */ });
if (wallet.data[0]?.wallet.balance < '5.00') {
// Still unpaid, cancel subscription
await lava.subscriptions.cancelActiveSubscription(active_subscription_id);
// Notify customer
await sendEmail({
to: wallet.data[0].wallet.email,
subject: 'Subscription Cancelled',
body: 'Your subscription was cancelled due to insufficient funds.'
});
}
}, 3 * 24 * 60 * 60 * 1000); // 3 days
res.json({ received: true });
});
Plan Upgrade/Downgrade
Handle tier changes:
async function changeSubscriptionPlan(
subscriptionId: string,
newConfigId: string
) {
// 1. Cancel existing subscription
await lava.subscriptions.cancelActiveSubscription(subscriptionId);
// 2. Create new checkout session for new plan
// (Customer will need to complete checkout for new plan)
const session = await lava.checkoutSessions.create({
checkout_mode: 'subscription',
origin_url: 'https://yourapp.com',
subscription_config_id: newConfigId
});
return session.checkout_session_token;
}
Plan changes require a new checkout session. There is no built-in upgrade/downgrade flow - you’ll need to cancel the old subscription and have the customer subscribe to the new plan.
Churn Analysis
Track subscription cancellations:
async function analyzeChurn(days: number = 30) {
const subs = await lava.subscriptions.listActiveSubscriptions();
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
const recentlyCancelled = subs.filter(sub =>
sub.cancelled_at &&
new Date(sub.cancelled_at) > cutoffDate
);
const activeAtStart = subs.filter(sub =>
new Date(sub.started_at) < cutoffDate
).length;
const churnRate = recentlyCancelled.length / activeAtStart;
return {
cancelledCount: recentlyCancelled.length,
activeCount: subs.filter(s => s.status === 'active').length,
churnRate: churnRate * 100, // Percentage
cancelledByPlan: recentlyCancelled.reduce((acc, sub) => {
const plan = sub.subscription_config.name;
acc[plan] = (acc[plan] || 0) + 1;
return acc;
}, {} as Record<string, number>)
};
}
Rollover Behavior
Full Rollover (rollover_type: 'full')
Unused credits carry over to the next billing period:
Month 1: Customer receives $25 credit, uses $15
→ Balance: $10 remaining
Month 2: Customer receives $25 + $10 rollover = $35 credit
Best for: Higher-tier plans, enterprise customers
No Rollover (rollover_type: 'none')
Unused credits are forfeited at the end of each period:
Month 1: Customer receives $10 credit, uses $6
→ Balance: $4 remaining (forfeited at period end)
Month 2: Customer receives $10 credit (fresh start)
Best for: Entry-level plans, predictable monthly costs