6 min read

How to Set Up Alerts for CAC Payback in Stripe

If you're tracking CAC payback—the time it takes a customer to generate enough revenue to cover their acquisition cost—Stripe doesn't calculate this automatically. You need to wire up webhooks and add custom logic to monitor when your CAC payback window is at risk. This guide walks you through setting alerts that fire when payback extends beyond your target.

Set Up a Webhook Listener for Subscription Events

Stripe webhooks give you real-time access to subscription and invoice events. You'll use these to track when customers generate revenue.

Create a webhook endpoint in your backend

Set up an HTTP POST endpoint that Stripe can call whenever subscription or invoice events occur. This is where you'll receive data about customer revenue.

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

app.post('/webhook', 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 === 'invoice.paid') {
    const invoice = event.data.object;
    console.log(`Invoice paid for customer ${invoice.customer}: $${invoice.total}`);
  }

  res.json({received: true});
});

app.listen(3000, () => console.log('Webhook listening'));
Express server listening for Stripe webhooks

Register the webhook endpoint in your Stripe Dashboard

Go to Developers > Webhooks in your Stripe Dashboard and add your endpoint URL. Select the events you want to listen for: invoice.paid, invoice.payment_failed, and customer.subscription.updated are the key ones for CAC payback tracking.

Calculate CAC Payback Period

When a customer makes a payment, compare their lifetime revenue against their acquisition cost. This tells you how many months until they've paid back what you spent to acquire them.

Query customer revenue from Stripe

Use the Stripe API to fetch all invoices for a customer. Sum up the paid amounts to get their lifetime revenue so far.

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

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

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

  return totalRevenue / 100; // Convert from cents to dollars
}

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

Extract MRR from the active subscription

Retrieve the customer's subscription and get the monthly recurring revenue from the price object. This tells you how much they're paying each month going forward.

javascript
async function getMonthlyMRR(customerId) {
  const customer = await stripe.customers.retrieve(customerId, {
    expand: ['subscriptions']
  });

  const subscription = customer.subscriptions.data[0];
  if (!subscription) return 0;

  const priceItem = subscription.items.data[0];
  const monthlyMRR = priceItem.price.unit_amount / 100; // Cents to dollars

  return monthlyMRR;
}

const mrr = await getMonthlyMRR('cus_ABC123');
console.log(`Monthly MRR: $${mrr}`);
Get the monthly recurring revenue from the subscription's price object

Calculate estimated payback months

Divide the CAC by monthly MRR to get how many months until payback. Compare this to your target. If it's extending beyond your threshold, the account is at risk.

javascript
async function getPaybackMetrics(customerId, cacAmount) {
  const mrr = await getMonthlyMRR(customerId);
  if (mrr === 0) return null;

  const paybackMonths = cacAmount / mrr;

  return {
    customerId,
    mrr,
    cac: cacAmount,
    paybackMonths: Math.round(paybackMonths * 10) / 10,
    isAtRisk: paybackMonths > 6
  };
}

const metrics = await getPaybackMetrics('cus_ABC123', 150);
if (metrics.isAtRisk) {
  console.log(`⚠️ Payback extended: ${metrics.paybackMonths} months`);
}
Determine if payback extends beyond your target threshold

Send Alerts When Payback Extends Beyond Target

Store target payback windows for your accounts. When calculated payback exceeds that threshold, notify your team immediately so they can investigate or intervene.

Integrate alert logic into your webhook handler

When an invoice.paid event arrives, check the customer's CAC payback metrics. If they're at risk, send a notification via Slack or email with the key details.

javascript
const axios = require('axios');

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

  if (event.type === 'invoice.paid') {
    const invoice = event.data.object;
    const cacData = await fetchCACData(invoice.customer);
    if (!cacData) return res.json({received: true});

    const metrics = await getPaybackMetrics(invoice.customer, cacData.cac);
    const targetPayback = cacData.target_payback_months || 6;

    if (metrics.paybackMonths > targetPayback) {
      await axios.post(process.env.SLACK_WEBHOOK_URL, {
        text: '⚠️ CAC Payback Alert',
        blocks: [{
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*Customer:* ${invoice.customer}\n*Payback:* ${metrics.paybackMonths}m (target: ${targetPayback}m)\n*MRR:* $${metrics.mrr}`
          }
        }]
      });
    }
  }

  res.json({received: true});
});
Listen for invoice.paid events and alert Slack when payback exceeds target

Add a cooldown to avoid alert spam

Track when you last sent an alert for each customer. Only send another alert after a set interval (e.g., 7 days) to prevent notification fatigue while maintaining visibility into at-risk accounts.

javascript
const lastAlertCache = {};

async function shouldSendAlert(customerId) {
  const lastAlert = lastAlertCache[customerId];
  if (!lastAlert) return true;

  const daysSinceAlert = (Date.now() - lastAlert) / (1000 * 60 * 60 * 24);
  return daysSinceAlert > 7; // Send alert only once per week
}

if (metrics.isAtRisk && (await shouldSendAlert(invoice.customer))) {
  // Send alert and cache the timestamp
  lastAlertCache[invoice.customer] = Date.now();
  // ... send Slack message ...
}
Implement cooldown to send alerts only once per week per customer
Watch out: When a customer downgrades their subscription, their MRR drops immediately but their CAC stays the same. Always recalculate payback metrics after subscription changes, not just on invoice.paid events.

Common Pitfalls

  • Forgetting that refunds and chargebacks reduce lifetime revenue but CAC stays constant—this makes payback appear to extend suddenly
  • Using the price amount directly without checking the billing interval; if a customer pays annually, monthly MRR is annual_amount / 12, not annual_amount
  • Not accounting for subscription downgrades or cancellations; payback metrics only apply to active subscriptions with positive MRR
  • Querying all invoices with limit: 100 when a customer has been active for years; implement pagination or use Stripe Sigma for high-volume accounts

Wrapping Up

You now have webhooks tracking customer payments and custom logic calculating CAC payback in real time. Your team gets alerts when acquisition spend isn't paying off quickly enough, letting you intervene early with at-risk accounts. If you want to track CAC payback automatically across all your tools and automate these alerts, Product Analyst can help.

Track these metrics automatically

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

Try Product Analyst — Free