Learn

How to track customer lifetime value

How do I track what a customer is worth over time, not per order?

Track lifetime value by resolving every payment and every session to one durable person — keyed on a user id or email rather than a browser cookie — and summing what that person has paid. The number only becomes actionable once you can trace it back to the channel that acquired them, which turns LTV from a finance metric into a marketing one.

Two different numbers get called LTV

Historical lifetime value is arithmetic over things that already happened: add up every payment one person has made, subtract refunds, stop. Predicted lifetime value is a forecast — an average order value, a repeat rate and a churn assumption, extrapolated forward. Both are useful. Only one of them can be badly wrong without telling you.

The sensitivity is worth seeing concretely. Assume 4% monthly churn and the average customer lasts 25 months (1 ÷ 0.04). Assume 6% and it is 17. Same product, same month, a third of the predicted value gone — and you will not find out which input was right for another year. The historical number is never wrong about the past; it is only ever incomplete.

 Historical LTVPredicted LTV
What it isMoney this person has actually paid, to dateA model of what they will pay
InputsYour payment records, joined to one identityOrder value, repeat rate, churn, a discount rate
Fails whenIdentity resolution is wrong — visibly, as split peopleAny assumption is wrong — invisibly, for months
Use it forRanking channels, defending a budgetLong-horizon planning, valuation

Track the historical number first. It is the one a small team can compute from data it already has, and the one that survives an argument. Statlark only computes that one: a person’s lifetime value is the sum of their recorded payments net of refunds, recomputed from the payments themselves rather than incremented, with no model in front of it.

A browser cookie cannot compute lifetime value

A cookie identifies a browser. Lifetime value is a property of a person, and the two stop agreeing almost immediately. The same customer reads your launch post on a phone, subscribes on a laptop three weeks later, clears storage in February, and renews from a second browser at work. That is four visitor ids, four first touches, and four rows that each look like a small, disappointing customer.

The damage lands where it costs most: your best customers have the longest relationships, so they are the likeliest to be split. Someone who paid $600 over eight months across three devices shows up as three $200 customers — none of them interesting enough to look at, and the channel that found them looks mediocre too.

Identity resolution needs one thing you almost certainly already have: a stable key. An id from your own database is strongest, as long as it never changes and is never reused — which rules out usernames and emails as the primary key. An email is the practical second, because it is what payment providers carry. Send it once per device, right after sign-in.

// Call this once per device, the moment you know who someone is —
// right after sign-in or signup, not on every page.
statlark.identify({
  user_id: "user_8412",          // your own stable id, never reused
  user_email: "jo@example.com",  // lets an email-only payment find this person
});

Statlark keys a person on the user id, the email, or both — at most one profile per user id and one per email for a site. When a later call proves that a user id and an email belong to the same human, the two profiles merge, keeping the older record and re-pointing the other one’s sessions and payments onto it, so a person stays one row with one timeline. The mirror image matters too: on a shared device, call statlark.reset()at sign-out, or the next person to log in inherits the last one’s sessions and revenue. Full mechanics in Identifying users.

The payment that arrives with only an email

The awkward case is also the common one. A subscription renews at 03:14, the webhook fires with an amount, a transaction id and a customer email, and there is no browser anywhere in the story. Invoices paid by transfer, orders your support team fixes by hand, and every renewal after the first look the same.

Only two joins are available. Either you carried your analytics visitor id through checkout so it comes back attached to the charge, or you match on the email — which works only if you told your analytics that person’s email earlier. The first is more accurate and covers buyers whose email you never learn; the second needs no checkout changes. Doing both is normal, and the more reliable signal should win.

Statlark resolves each payment in a fixed precedence: the Stripe client_reference_id stamped on the checkout session, then session metadata, then the subscription metadata that rides on every invoice a subscription raises, then the email stitch. A payment carrying only an email finds or creates that person, even if its browser never identified anyone, and the revenue joins their lifetime value. From your own server, the same field over HTTP:

# A renewal invoice with no browser attached: send the email and let it stitch.
curl -X POST "https://app.statlark.com/api/v1/payments" \
  -H "Authorization: Bearer slk_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "in_1QX...",
    "amount": 29.00,
    "currency": "USD",
    "customer_email": "jo@example.com"
  }'

Where this genuinely breaks: a customer who pays with a different address from the one they browsed with is two people, and no tool can tell. Email stitching is only as good as the emails matching, which is why stamping the visitor id through checkout is worth it wherever you control that code — the Stripe join has that code, and the normalisation rules that decide whether two addresses count as one.

Lifetime value by acquisition channel

Site-wide LTV is a slide. The version that changes a decision splits lifetime value by how the customer was acquired — and it is the cut almost nobody has, because it needs the acquisition source still attached to the person months after the visit that earned them.

Say customers who first arrived through paid search average $180 of lifetime value, and customers who arrived through your newsletter average $290. No traffic report would have told you that; the newsletter probably sends a tenth of the visitors. What it changes is the ceiling on each — the same $90 acquisition cost is comfortable in one column and marginal in the other.

This only works if the first touch is frozen. If the source field is overwritten on the visit where someone finally buys, every long consideration path collapses into direct or branded search, and the channels that do the finding get credited to the channels that do the closing (the trade-off in first-touch vs last-touch).

Statlark freezes the first source, medium, referrer and landing path on a visitor’s very first hit and sorts them into six channels — Ads, Email, Social, Organic, Referral and Direct. A person inherits the channel of their earliest device active in the range, so the acquiring channel sits on the same row as the lifetime value in the People card’s full list, which ranks people by LTV; filter the Overview to a channel and that list narrows to the people it brought. There is no average-LTV-per-channel figure — you read it off those rows. Related: attributing revenue to marketing channels and revenue per visitor, which asks the same question over a window instead of a lifetime.

LTV without acquisition cost is half a sentence

$400 of lifetime value is excellent at a $50 acquisition cost and a slow way to run out of money at $380. The quoted 3:1 ratio is a rule of thumb; what actually constrains a small company is payback period, because it is denominated in cash rather than in eventual value. Payback is acquisition cost divided by the gross profit that customer produces per month — gross profit, not revenue.

ChannelMean LTVCost per customerLTV : CACGross profit / moPayback
Paid search$180$1201.5 : 1$158 months
Content$310$853.6 : 1$175 months
Newsletter$290$407.3 : 1$202 months

The paid-search row is the one to act on, and not because 1.5:1 is a disaster: it takes eight months to return the cash it consumed, which is a different decision on a twelve-month runway than on a forty-month one.

Statlark does not import ad spend, so it has no ROAS figure and cannot compute payback for you. Acquisition cost comes from the ad platform or your ledger; analytics owes you the other half — how many customers a channel produced and what they went on to pay. The division happens in your spreadsheet.

Mixed currencies quietly ruin the number

Sell in more than one currency and a naive sum stops being money: 100 EUR plus 100 USD is not 200 of anything, and every metric built on it — lifetime value, revenue per visitor, the channel ranking — inherits the error without looking wrong.

There are two defensible answers. Convert into one reporting currency, which means committing to a rate and a date for it and accepting that last month’s report will not reproduce today. Or refuse to mix, and report in one currency. The first buys completeness at the cost of reproducibility.

Statlark takes the second. Each site has one currency, chosen when you add the site, and a payment in any other currency is recorded but excluded from every revenue rollup — Overview headline, source ranking and lifetime value alike. Nothing is converted at a rate nobody chose, and the cost is explicit: a multi-currency business has to normalize before it sends the payment, converting at whatever rate it considers authoritative and posting the amount in the site currency.

Where to start

Each step is worth something on its own. Call identify with a stable id at sign-in, so devices stop fragmenting into strangers. Make sure every payment carries the visitor id or the customer’s email, so revenue lands on a person rather than in an unattributed pile. Then check whether the acquiring channel on your highest-value people matches where the budget goes — usually the comparison that pays for the whole exercise.

For what each number on the screen means and how the channels are classified, see Reading your data.

Last reviewed