6 min read

How to Monitor CAC Payback in Stripe

You're spending money on ads, but when does that investment actually pay off? CAC payback is when a customer's lifetime value covers what you spent to acquire them. In Stripe, this means connecting your marketing costs to the revenue each customer generates—and Stripe's APIs and webhooks make this surprisingly tractable.

Tag Customers with Acquisition Metadata

Every customer who converts should carry metadata about how you acquired them—campaign, cost, date. Stripe's customer metadata is the place to store this.

Store acquisition cost and source on customer creation

When a customer signs up, create a Stripe customer with metadata that includes acquisition cost, campaign source, and conversion date. This becomes the baseline for your CAC payback calculation.

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const customer = await stripe.customers.create({
  email: '[email protected]',
  metadata: {
    acquisition_cost: '50',        // Cost paid to acquire this customer (e.g., $50 ad spend)
    acquisition_source: 'google_ads',
    acquisition_date: new Date().toISOString(),
    campaign_id: 'campaign_12345'
  }
});
Create a customer with acquisition metadata

Use webhooks to capture customer signup in real time

Listen for customer.created events to log when a conversion happens. This ensures acquisition data is captured the moment the customer is created.

javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

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

  if (event.type === 'customer.created') {
    const customer = event.data.object;
    console.log(`Customer ${customer.id} acquired:`, customer.metadata);
    // Log to your analytics backend
  }

  res.json({received: true});
});
Capture customer signup events via Stripe webhooks
Tip: Store acquisition_cost as a string in metadata (Stripe only accepts strings), then convert to a number when calculating payback.

Calculate Lifetime Value from Subscription Revenue

Once a customer is tagged with acquisition data, track what they've spent. Query Stripe's subscriptions and invoices to calculate total revenue.

Retrieve subscription history and sum revenue

Use subscriptions.list() filtered by customer to pull all subscriptions. Then fetch invoices.list() to get the actual amount paid over time.

javascript
async function getCustomerLifetimeValue(customerId) {
  const invoices = await stripe.invoices.list({
    customer: customerId,
    status: 'paid',  // Only count invoices that were actually paid
    limit: 100
  });

  let totalRevenue = 0;
  for (const invoice of invoices.data) {
    totalRevenue += invoice.amount_paid / 100; // Convert from cents to dollars
  }

  return totalRevenue;
}

const ltv = await getCustomerLifetimeValue('cus_ABC123');
console.log(`Customer LTV: $${ltv}`);
Sum paid invoices to calculate lifetime value

Calculate months until CAC payback

Compare acquisition cost to cumulative revenue month-by-month. Find the invoice date when cumulative revenue first exceeded the acquisition cost.

javascript
async function getCAcPaybackMonths(customerId) {
  const customer = await stripe.customers.retrieve(customerId);
  const acquisitionCost = parseFloat(customer.metadata.acquisition_cost);
  const acquisitionDate = new Date(customer.metadata.acquisition_date);

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

  let cumulativeRevenue = 0;
  let paybackDate = null;

  for (const inv of invoices.data.sort((a, b) => a.created - b.created)) {
    cumulativeRevenue += inv.amount_paid / 100;
    if (cumulativeRevenue >= acquisitionCost && !paybackDate) {
      paybackDate = new Date(inv.created * 1000);
      break;
    }
  }

  if (paybackDate) {
    const months = Math.round((paybackDate - acquisitionDate) / (1000 * 60 * 60 * 24 * 30));
    return months;
  }
  return null; // Not yet paid back
}
Find the exact month when customer revenue covered acquisition cost
Watch out: Stripe's amount_paid can include refunds applied after the fact. Always filter by status: 'paid' to avoid counting reversed invoices.

Build Cohort Analysis by Acquisition Source

Individual payback periods are useful, but patterns emerge when you group by campaign. Use Stripe's search API to query customers by metadata.

Query customers by acquisition source and calculate aggregate payback

Use customers.search() to filter by metadata, then calculate average payback time for each campaign. This shows you which channels deliver the best ROI.

javascript
async function getPaybackByCohort(acquisitionSource) {
  const customers = await stripe.customers.search({
    query: `metadata['acquisition_source']:'${acquisitionSource}'`,
    limit: 100
  });

  let paybackMonths = [];
  for (const customer of customers.data) {
    const months = await getCAcPaybackMonths(customer.id);
    if (months !== null) paybackMonths.push(months);
  }

  const avgPayback = paybackMonths.length ?
    (paybackMonths.reduce((a, b) => a + b) / paybackMonths.length).toFixed(1) :
    'N/A';

  return {
    source: acquisitionSource,
    totalCustomers: customers.data.length,
    paidBackCustomers: paybackMonths.length,
    avgPaybackMonths: avgPayback
  };
}

const cohort = await getPaybackByCohort('google_ads');
console.log(cohort); // {source: 'google_ads', totalCustomers: 42, paidBackCustomers: 38, avgPaybackMonths: '2.3'}'
Aggregate CAC payback metrics by marketing source
Tip: Use Stripe's dashboard Customers page—filter by metadata and sort by Created to see acquisition source right there without querying the API.

Common Pitfalls

  • Treating metadata as mutable—acquisition cost and date are set at customer creation and can't be changed. Plan for immutability upfront.
  • Counting refunded invoices toward LTV—always filter by status: 'paid' and check amount_paid instead of amount_charged.
  • Not accounting for churn—a customer who canceled month 3 has different payback math than one still active. Track churn separately.
  • Mixing test and live mode data—add livemode: true to your search query to exclude test customers from cohort analysis.

Wrapping Up

CAC payback requires connecting acquisition costs, subscription data, and timelines. By tagging customers at signup and calculating payback from invoice history, you can see exactly which campaigns deliver value. 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