Your recurring revenue business lives or dies by customer LTV. Stripe has all the data you need to calculate it, but it doesn't hand you an LTV number directly—you have to assemble it from invoices and subscription history. Here's how.
What LTV Actually Means in Stripe
LTV is the total revenue a customer generates over the entire relationship. In Stripe, this means summing all successful payments—subscriptions, one-time charges, whatever.
Understand the Stripe customer data model
A Customer in Stripe is the container for all transactions. Every invoice, charge, and subscription attaches to a customer ID. To calculate LTV, you fetch a customer and sum their amount_paid across all paid invoices.
const stripe = require('stripe')('sk_live_...');
// Fetch a customer and their paid invoices
const customer = await stripe.customers.retrieve('cus_...');
const invoices = await stripe.invoices.list({
customer: customer.id,
status: 'paid',
limit: 100
});
const ltv = invoices.data.reduce((sum, inv) => sum + inv.amount_paid, 0) / 100;Remember LTV includes all revenue, not just subscriptions
Don't limit yourself to subscription invoices. Include one-time charges, manual invoices, and anything else the customer paid. The invoices.list() call with status: 'paid' captures all of it.
// Get paid invoices across all revenue sources
const allPaidInvoices = await stripe.invoices.list({
customer: 'cus_...',
status: 'paid',
limit: 100
});
const totalLTV = allPaidInvoices.data.reduce((sum, inv) => {
return sum + inv.amount_paid;
}, 0) / 100;
console.log(`Total customer LTV: $${totalLTV.toFixed(2)}`);amount_paid on invoices accounts for refunds automatically—it's net revenue. If you want gross revenue before refunds, use the Charges API and sum amount instead.Calculate LTV from payment history
In practice, you'll query Stripe's API to pull all a customer's invoices, then sum them. Here's the working pattern.
List all paid invoices for a customer
Use the Invoices API with status: 'paid' to get only money that cleared. If a customer has more than 100 invoices, paginate using starting_after.
async function getCustomerLTV(customerId) {
let allInvoices = [];
let hasMore = true;
let startingAfter = null;
while (hasMore) {
const response = await stripe.invoices.list({
customer: customerId,
status: 'paid',
limit: 100,
starting_after: startingAfter
});
allInvoices = allInvoices.concat(response.data);
hasMore = response.has_more;
startingAfter = response.data[response.data.length - 1]?.id;
}
const ltv = allInvoices.reduce((sum, inv) => sum + inv.amount_paid, 0) / 100;
return ltv;
}
const ltv = await getCustomerLTV('cus_...');Sum up the paid amounts
Add up amount_paid from each invoice to get net revenue (after refunds). Divide by 100 to convert from cents.
const ltv = invoices.reduce((sum, invoice) => {
return sum + invoice.amount_paid;
}, 0) / 100;
console.log(`Total LTV: $${ltv.toFixed(2)}`);Use the Charges API for gross revenue
If you need gross revenue before refunds, query Charges and filter by status: 'succeeded'. Subtract refunds separately if needed.
const charges = await stripe.charges.list({
customer: 'cus_...',
limit: 100
});
const grossRevenue = charges.data
.filter(charge => charge.status === 'succeeded')
.reduce((sum, charge) => sum + charge.amount, 0) / 100;
console.log(`Gross revenue: $${grossRevenue.toFixed(2)}`);Rank customers by LTV
Once you can calculate LTV, segment your customers by value. Batch-fetch and rank them to identify your top spenders.
Fetch all customers and calculate LTV for each
List all customers, then calculate LTV for each one. Sort by value to identify your high-value accounts.
async function getAllCustomerLTVs() {
let customers = [];
let startingAfter = null;
while (true) {
const response = await stripe.customers.list({
limit: 100,
starting_after: startingAfter
});
for (const customer of response.data) {
const invoices = await stripe.invoices.list({
customer: customer.id,
status: 'paid'
});
const ltv = invoices.data.reduce((s, i) => s + i.amount_paid, 0) / 100;
customers.push({ id: customer.id, email: customer.email, ltv });
}
if (!response.has_more) break;
startingAfter = response.data[response.data.length - 1].id;
}
return customers.sort((a, b) => b.ltv - a.ltv);
}
const ranked = await getAllCustomerLTVs();
console.log('Top 10 customers:', ranked.slice(0, 10));Common Pitfalls
- Forgetting to paginate—customers with 100+ invoices return incomplete results without
starting_afterloops. - Using
totalinstead ofamount_paid—totalincludes amounts due and refunded amounts;amount_paidis actual net revenue. - Mixing invoices and charges—invoices handle subscriptions and multi-line items; charges are one-offs. Include both for true LTV.
- Calculating LTV once and treating it as static—LTV changes every time a customer pays, so recalculate regularly for current analysis.
Wrapping Up
LTV in Stripe is just the sum of what a customer paid you, calculated from the Invoices API with status: 'paid'. Start with a single customer to test, then batch-fetch for all customers to rank by value. If you want to track LTV automatically across Stripe, Mixpanel, Amplitude, and other tools, Product Analyst can help.