Stripe doesn't calculate LTV for you—you need to query your payment data and run the math yourself. If you're tracking revenue health and customer value, LTV is essential, but it's hidden in your customer records until you pull it out.
Retrieve All Customer Payments
LTV starts with knowing what a customer has actually paid. Stripe stores every charge, refund, and invoice, so you can reconstruct the full payment history.
Step 1: Install the Stripe Node SDK
Add the official Stripe library to your project. Use npm install stripe or pnpm add stripe depending on your package manager.
npm install stripeStep 2: Query a customer's charges
Use the Charges API to fetch all successful payments for a specific customer. The stripe.charges.list() method returns paginated results, so you'll need to handle pagination if a customer has many transactions.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function getCustomerCharges(customerId) {
let allCharges = [];
let hasMore = true;
let startingAfter = null;
while (hasMore) {
const charges = await stripe.charges.list({
customer: customerId,
limit: 100,
starting_after: startingAfter,
});
allCharges = allCharges.concat(charges.data);
hasMore = charges.has_more;
if (hasMore) {
startingAfter = charges.data[charges.data.length - 1].id;
}
}
return allCharges;
}Step 3: Filter for successful payments
Charge objects include failed attempts and refunds. Filter to only count status: 'succeeded' to get real revenue that actually hit your account.
const successfulCharges = allCharges.filter(
charge => charge.status === 'succeeded' && !charge.refunded
);
const totalInCents = successfulCharges.reduce((sum, c) => sum + c.amount, 0);
const totalInDollars = totalInCents / 100;
console.log(`Found ${successfulCharges.length} successful charges`);
console.log(`Total revenue: $${totalInDollars.toFixed(2)}`);
invoice field—if it's populated, that's a subscription payment. If null, it's a one-off charge.Calculate Customer Lifetime Value
LTV is the total net amount a customer has paid you. Stripe doesn't give you this directly, so you calculate it from successful charges minus refunds.
Step 1: Sum charges and subtract refunds
Stripe tracks refunds separately. For each charge, add the amount, then subtract any refunded amounts using the refunds array on the charge object.
function calculateLTV(charges) {
let totalRevenue = 0;
charges.forEach(charge => {
if (charge.status === 'succeeded') {
totalRevenue += charge.amount;
if (charge.refunded && charge.refunds.data.length > 0) {
charge.refunds.data.forEach(refund => {
totalRevenue -= refund.amount;
});
}
}
});
return totalRevenue / 100; // Convert from cents to dollars
}
const ltv = calculateLTV(successfulCharges);
console.log(`Customer LTV: $${ltv.toFixed(2)}`);
Step 2: Split LTV by payment type
If you want to track recurring vs. one-time revenue separately, filter charges by the invoice field. Charges with an invoice ID are subscription-related; those without are one-offs.
const subscriptionCharges = allCharges.filter(c => c.invoice);
const oneOffCharges = allCharges.filter(c => !c.invoice);
const subscriptionLTV = calculateLTV(subscriptionCharges);
const oneOffLTV = calculateLTV(oneOffCharges);
console.log(`Subscription LTV: $${subscriptionLTV.toFixed(2)}`);
console.log(`One-off LTV: $${oneOffLTV.toFixed(2)}`);
console.log(`Total LTV: $${(subscriptionLTV + oneOffLTV).toFixed(2)}`);
Automate LTV Monitoring
Calculating LTV once is useful, but you need to track it over time. Set up a webhook listener that updates LTV whenever a charge succeeds.
Step 1: Create a reusable LTV function
Wrap your LTV calculation in a function that takes a customer ID and returns the current LTV. This is what you'll call from webhooks or scheduled jobs.
async function getLTVForCustomer(customerId) {
const charges = await getCustomerCharges(customerId);
return calculateLTV(charges);
}
module.exports = { getLTVForCustomer };
Step 2: Listen for charge.succeeded webhooks
Use Stripe Webhooks to listen for charge.succeeded events. When a charge goes through, recalculate and store the updated LTV. Set up your endpoint in the Stripe Dashboard under Developers > Webhooks.
const express = require('express');
const app = express();
const { getLTVForCustomer } = require('./ltv');
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'charge.succeeded') {
const customerId = event.data.object.customer;
const ltv = await getLTVForCustomer(customerId);
console.log(`Customer ${customerId} LTV: $${ltv.toFixed(2)}`);
// Store in your database here
}
res.json({received: true});
});
Step 3: Persist LTV snapshots for trending
Save each LTV calculation (customer ID, amount, timestamp) to your own database or metrics backend. This lets you track how LTV changes over time and identify your best customers.
// After calculating LTV from a webhook event
async function saveLTVSnapshot(customerId, ltv) {
await db.query(
'INSERT INTO customer_ltv (customer_id, ltv_amount, recorded_at) VALUES ($1, $2, $3)',
[customerId, ltv, new Date()]
);
console.log(`Saved LTV for ${customerId}: $${ltv.toFixed(2)}`);
}
// Call after every charge.succeeded
await saveLTVSnapshot(customerId, ltv);
Common Pitfalls
- Forgetting to filter for
status: 'succeeded'—failed charges will artificially lower your LTV if included. - Not accounting for refunds—a large refund that you ignore will make LTV wrong and your reporting unreliable.
- Mixing subscription and one-off revenue—these represent different customer segments and need separate tracking for meaningful analysis.
- Querying all customers at once on a schedule—you'll hit Stripe API rate limits. Use webhooks instead to update LTV as payments occur.
Wrapping Up
LTV in Stripe requires you to query and aggregate your own data, but it's straightforward once you know where to look. Pull charges from the API, filter for successes, subtract refunds, and set up webhooks to track changes automatically. If you want to track this automatically across tools and integrate it with your other product metrics, Product Analyst can help.