6 min read

How to Visualize LTV in Stripe

Stripe's dashboard shows MRR and revenue metrics, but doesn't natively display customer lifetime value. If you're running a subscription business, you need to see which customers are actually worth the most over time—that requires pulling raw transaction data and calculating it yourself.

Fetch Customer Spending Data via the Stripe API

The foundation for any LTV calculation is getting the total amount each customer has paid. Stripe's API lets you list all charges or invoices for a specific customer.

Retrieve a customer and sum their charges

Use the Stripe SDK to fetch a customer by ID, then list all charges associated with that customer. The total amount from all charges is their raw lifetime value.

javascript
const stripe = require('stripe')('sk_live_...');

// Get all charges for a customer
const charges = await stripe.charges.list({
  customer: 'cus_ABC123',
  limit: 100
});

const totalSpent = charges.data.reduce((sum, charge) => {
  return sum + charge.amount;
}, 0) / 100; // Convert from cents to dollars

console.log(`Customer LTV: $${totalSpent}`);

// For subscriptions, also check paid invoices
const invoices = await stripe.invoices.list({
  customer: 'cus_ABC123',
  status: 'paid'
});

const invoiceTotal = invoices.data.reduce((sum, invoice) => {
  return sum + invoice.total;
}, 0) / 100;

console.log(`Subscription total: $${invoiceTotal}`);
Query a specific customer's charges and invoices to calculate total lifetime spend

Batch export all customer spend for a custom dashboard

To visualize LTV across your entire customer base, run this calculation for all customers. Export the results to CSV and import into any visualization tool.

javascript
const stripe = require('stripe')('sk_live_...');
const fs = require('fs');
const csv = require('csv-stringify/sync');

const results = [];
const customers = await stripe.customers.list({ limit: 100 });

for (const customer of customers.data) {
  const charges = await stripe.charges.list({
    customer: customer.id,
    limit: 100
  });
  
  const totalSpent = charges.data
    .filter(charge => !charge.refunded)
    .reduce((sum, charge) => sum + charge.amount, 0) / 100;
  
  results.push({
    customer_id: customer.id,
    email: customer.email,
    ltv: totalSpent.toFixed(2),
    created_at: new Date(customer.created * 1000).toISOString()
  });
}

const csvContent = csv.stringify(results, { header: true });
fs.writeFileSync('customer_ltv.csv', csvContent);
console.log('Exported to customer_ltv.csv');
Batch query all customers and export their LTV to CSV, excluding refunded charges
Watch out: Stripe's API returns amounts in cents. Always divide by 100 to get dollar values. Also, use starting_after with the last customer ID if you have more than 100 customers to paginate through the full list.

Visualize LTV in a Dashboard

Once you have customer spend data exported, import it into a visualization tool and sort by LTV to see your highest-value accounts.

Import CSV into Google Sheets and sort by LTV

Upload your CSV to Google Sheets, add a sort rule to rank customers by LTV descending, then create a bar chart to visualize the distribution of customer value.

javascript
// Programmatically append customer LTV data to Google Sheets
const { google } = require('googleapis');
const sheets = google.sheets({ version: 'v4', auth: authClient });

await sheets.spreadsheets.values.append({
  spreadsheetId: 'your_sheet_id',
  range: 'Sheet1!A1',
  valueInputOption: 'RAW',
  requestBody: {
    values: [
      ['Customer ID', 'Email', 'LTV', 'Created Date'],
      ['cus_ABC123', '[email protected]', '5000', '2023-01-15']
    ]
  }
});
Append customer LTV data to Google Sheets via the API

Keep your dashboard updated with webhooks

Listen to charge.succeeded and invoice.payment_succeeded webhook events. Each time a payment arrives, update your database with the new charge and recalculate that customer's LTV.

javascript
const stripe = require('stripe')('sk_live_...');
const express = require('express');

app.post('/stripe-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const event = stripe.webhooks.constructEvent(
    req.body,
    sig,
    'whsec_...'
  );

  if (event.type === 'charge.succeeded') {
    const charge = event.data.object;
    // Update your database: add charge amount to customer's LTV
    console.log(`Charge: ${charge.id}, Customer: ${charge.customer}, Amount: $${charge.amount / 100}`);
  }

  if (event.type === 'invoice.payment_succeeded') {
    const invoice = event.data.object;
    // Update database: add invoice total to customer's LTV
    console.log(`Invoice paid: ${invoice.id}, Customer: ${invoice.customer}`);
  }

  res.json({ received: true });
});
Listen to Stripe webhooks to update LTV in real-time as payments arrive
Tip: Your top 20% of customers by LTV typically generate 80% of revenue. Segment them and measure retention impact—this becomes your most important metric.

Segment LTV: Recurring vs One-Time

If you have a mix of subscriptions and one-off purchases, segment LTV by type to understand how much revenue is predictable.

Separate subscription invoices from one-time charges

Query invoices (recurring subscription payments) separately from charges (one-time payments). This reveals the mix of recurring vs variable revenue per customer.

javascript
const stripe = require('stripe')('sk_live_...');

const customerId = 'cus_ABC123';

// Recurring revenue from paid invoices
const invoices = await stripe.invoices.list({
  customer: customerId,
  status: 'paid'
});

const recurringLTV = invoices.data.reduce((sum, inv) => {
  return sum + inv.total;
}, 0) / 100;

// One-time charges (not tied to an invoice)
const charges = await stripe.charges.list({
  customer: customerId
});

const oneTimeLTV = charges.data
  .filter(charge => !charge.invoice && !charge.refunded)
  .reduce((sum, charge) => sum + charge.amount, 0) / 100;

const totalLTV = recurringLTV + oneTimeLTV;

console.log(`Recurring: $${recurringLTV}`);
console.log(`One-time: $${oneTimeLTV}`);
console.log(`Total LTV: $${totalLTV}`);
console.log(`Recurring %: ${(recurringLTV / totalLTV * 100).toFixed(1)}%`);
Calculate recurring vs one-time LTV separately to understand revenue stability

Common Pitfalls

  • Not paginating through customers or charges—Stripe returns 100 results by default. If you have more, use starting_after to fetch the next batch or you'll miss customers in your LTV calculation.
  • Counting refunded charges as revenue—refunded charges still appear in the list. Check charge.refunded status and subtract charge.amount_refunded to get actual collected revenue.
  • Double-counting subscription revenue—invoices and charges overlap for subscription payments. Filter by charge.invoice to separate one-time charges from subscription invoices.
  • Including unpaid invoices in your LTV—invoices with status 'draft' or 'open' are not collected revenue. Always filter to status: 'paid' to count only money that actually arrived.

Wrapping Up

You now have a method to calculate and visualize customer LTV in Stripe—pull all charges and invoices per customer, export to CSV, and visualize the distribution. From there, identify your top customers, segment by recurring vs one-time, and use retention metrics to track LTV growth. If you want to track this automatically across tools, Product Analyst can help.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free