5 min read

How to Track LTV in Stripe

Stripe doesn't calculate LTV for you—but your payment data is already there. You need to know which customers are actually worth your attention, and that means pulling subscription revenue, historical payments, and churn signals into a single view. Here's how to build that.

Store LTV as customer metadata

The simplest approach: calculate LTV once, store it on the customer object, and update it as revenue changes.

Create a customer with LTV metadata

When you onboard a customer or process their first payment, add an ltv field to their metadata. This makes it queryable later and keeps all customer data in one place.

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

const customer = await stripe.customers.create({
  email: '[email protected]',
  name: 'Jane Doe',
  metadata: {
    ltv: '0',
    signup_date: new Date().toISOString(),
    account_type: 'premium'
  }
});
Initialize LTV metadata when creating a new customer

Update LTV after calculating revenue

Once you've summed up paid invoices for a customer, push that back to Stripe. Use the Customers page in the Stripe Dashboard to see metadata in real-time.

javascript
await stripe.customers.update(customerId, {
  metadata: {
    ltv: '1250.00',
    ltv_last_updated: new Date().toISOString(),
    churn_risk: 'low'
  }
});
Update customer metadata with calculated LTV
Watch out: Metadata is limited to 50 keys and 500 characters total. For detailed LTV history, store it in your own database instead.

Query and calculate LTV from subscription data

Stripe subscriptions are the source of truth for recurring revenue. Pull subscription status, monthly charges, and invoice totals to calculate real LTV.

Fetch all subscriptions for a customer

List all active and past subscriptions to understand revenue history. The subscription object includes current_period_start, current_period_end, and plan amount.

javascript
const subscriptions = await stripe.subscriptions.list({
  customer: customerId,
  status: 'all'
});

let totalMrr = 0;
subscriptions.data.forEach(sub => {
  if (sub.status === 'active') {
    totalMrr += sub.plan.amount / 100;
  }
  console.log(`Plan: $${sub.plan.amount / 100}, Status: ${sub.status}`);
});
Retrieve all subscriptions and sum active MRR

Sum invoice totals to get cumulative revenue

Invoices represent actual money collected. Query by customer and status to get paid invoices, then total the amounts.

javascript
const invoices = await stripe.invoices.list({
  customer: customerId,
  status: 'paid',
  limit: 100
});

let totalRevenue = 0;
invoices.data.forEach(invoice => {
  totalRevenue += invoice.amount_paid;
});

const ltv = (totalRevenue / 100).toFixed(2);
console.log(`Customer LTV: $${ltv}`);
Calculate LTV by summing paid invoices

Account for refunds and adjustments

Paid invoices don't tell the full story if the customer has refunds. Query charges to see the net amount after refunds are applied.

javascript
const charges = await stripe.charges.list({
  customer: customerId,
  limit: 100
});

let netRevenue = 0;
charges.data.forEach(charge => {
  const chargeNet = charge.amount_captured - charge.amount_refunded;
  netRevenue += chargeNet;
});

const adjustedLtv = (netRevenue / 100).toFixed(2);
console.log(`LTV after refunds: $${adjustedLtv}`);
Calculate net revenue after refunds
Tip: Stripe returns amounts in cents. Always divide by 100 before displaying currency. Store LTV with a timestamp so you can track changes over time.

Automate LTV updates with webhooks

Manual queries work once, but LTV changes constantly. Set up webhooks to recalculate and update LTV whenever a customer makes a payment, cancels, or refunds.

Listen for subscription and payment events

Create a webhook endpoint that responds to invoice.paid, charge.refunded, and customer.subscription.deleted. These events fire every time revenue changes.

javascript
const express = require('express');
const app = express();

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

  if (['invoice.paid', 'charge.refunded', 'customer.subscription.deleted'].includes(event.type)) {
    const customerId = event.data.object.customer;
    await updateCustomerLtv(customerId);
  }

  res.status(200).json({received: true});
});
Set up a webhook endpoint to listen for payment events

Recalculate LTV inside your webhook handler

When a webhook fires, query the customer's invoices again, recalculate LTV, and update metadata. This keeps Stripe in sync with your latest numbers.

javascript
async function updateCustomerLtv(customerId) {
  const invoices = await stripe.invoices.list({
    customer: customerId,
    status: 'paid'
  });

  const ltv = invoices.data.reduce((sum, inv) => sum + inv.amount_paid, 0) / 100;

  await stripe.customers.update(customerId, {
    metadata: {
      ltv: ltv.toFixed(2),
      ltv_last_updated: new Date().toISOString()
    }
  });

  console.log(`Updated LTV for ${customerId}: $${ltv.toFixed(2)}`);
}
Recalculate LTV when webhook fires

Register your webhook in the Dashboard

Go to Developers > Webhooks in the Stripe Dashboard, add your endpoint URL, and select the events: invoice.paid, charge.refunded, and customer.subscription.deleted.

Watch out: Webhooks can arrive out of order or duplicate. Always query the latest state from Stripe rather than trusting the event data alone.

Common Pitfalls

  • Forgetting to account for refunds—LTV based on invoice totals alone overstates customer value when refunds occur.
  • Querying without pagination limits—Stripe returns up to 100 items per request; for long customer histories, you'll miss data without looping through all pages.
  • Storing LTV in metadata then never updating it—metadata is a snapshot, not real-time; you need webhooks to keep it current.
  • Calculating LTV as MRR × expected months—subscription cancellations are unpredictable; actual LTV is historical revenue, not a projection.

Wrapping Up

You now have LTV stored on every customer in Stripe and a webhook that updates it automatically. The next step is connecting this to churn prediction or cohort analysis—seeing which LTV tiers are at risk of canceling, or how LTV changes over time. If you want to track this automatically across tools and connect it to your product metrics, Product Analyst can help.

Track these metrics automatically

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

Try Product Analyst — Free