Ecommerce

How do you calculate contribution margin LTV by acquisition channel?

Group customers by first order month and channel, compare cohorts at the same age, and subtract variable costs and CAC before you move spend.

Linus Tse

Greyfield Data

6 min read
In this article 6 sections

Group customers by the month of their first order and the channel that acquired them. Track each group at the same age. For each month, take net sales, add shipping charged, and subtract cost of goods, fulfillment, and payment fees. Add that up, divide by customers acquired, and subtract CAC.

That gives you observed contribution per customer. Revenue LTV can rank your channels in a different order, and it isn't enough on its own for a spend decision.

Here's how.

1. Define the cohort before you touch the numbers

You need three decisions in writing.

Cohort date. Use the customer's first order date. Shopify's customer cohort analysis report groups customers the same way, so you can use it as a check on customer counts.

Channel. Pick one rule and hold it steady. First touch is a common choice. Shopify exposes visit data through customerJourneySummary, including the first visit. Some orders will come back with thin or missing source data, so report an "unknown" channel and don't spread it across the others.

Age. Month 0 is the month of the first order. Month 6 is six months later. Only compare cohorts that have all reached the same month.

2. Define contribution

Shopify's definition is revenue minus variable costs. For this report, use:

contribution = net sales + shipping charged - cost of goods - fulfillment - payment fees

Shopify net sales leave out shipping charges. Use net sales after discounts and refunds. Include other costs only if they rise with each order, such as packaging. Leave out rent, salaries, and software. They matter, but they don't change when you add one more customer.

Shopify's profit reports only cover products with a cost recorded at the time of sale, and the cost field is static. If your costs change, keep a dated cost table in the warehouse. Shipping and payment fees usually come from other systems, so plan to load those too.

3. Add up the months and subtract CAC

For each channel:

CAC = acquisition spend / new customers acquired

contribution after CAC = cumulative contribution per customer - CAC

The spend and the customers should cover the same period. If a campaign in one month acquires customers in the next, write down how you handle it.

This query takes one row per cohort month, channel, and month of age:

with monthly as (
  select cohort_month, channel, month_number, customers, net_sales,
         net_sales + shipping_charged - cogs - fulfillment - payment_fees as contribution
  from cohort_monthly
)
select m.cohort_month, m.channel, m.month_number, m.customers,
       round(sum(m.net_sales) over (partition by m.cohort_month, m.channel order by m.month_number) / m.customers, 2) as cumulative_sales_per_customer,
       round(sum(m.contribution) over (partition by m.cohort_month, m.channel order by m.month_number) / m.customers, 2) as cumulative_contribution_per_customer,
       round(s.spend / m.customers, 2) as cac,
       round((sum(m.contribution) over (partition by m.cohort_month, m.channel order by m.month_number) - s.spend) / m.customers, 2) as contribution_after_cac
from monthly m
left join channel_spend s on s.cohort_month = m.cohort_month and s.channel = m.channel
order by m.cohort_month, m.channel, m.month_number

Keep the cohort size fixed at the number first acquired. If you divide by customers who are still active, your LTV goes up as customers leave.

A worked example

These numbers are made up. The cohort is one month of new customers, tracked through month 6. I assumed cost of goods of 40% of net sales for paid social, 38% for paid search, and 36% for organic and referral. Customers paid no shipping, so shipping charged is $0. Fulfillment is $7 per order and payment fees are 3% of net sales.

Paid social's first month works like this: $62.00 net sales, minus $24.80 cost of goods, minus $7.00 fulfillment, minus $1.86 fees. That's $28.34 of contribution per customer.

Channel Customers Spend CAC Sales per customer Contribution per customer After CAC
Paid social 400 $18,000 $45 $126.00 $58.59 $13.59
Paid search 250 $7,500 $30 $100.00 $47.73 $17.73
Organic and referral 150 Not measured Not measured $107.00 $52.88 Not measured

All figures are cumulative through month 6. I ran the query on this sample data in SQLite and the table matches its output. Null shows as "Not measured".

Paid social leads on sales, $126 to $100. After costs and CAC, paid search leads, $17.73 to $13.59. Paid search also pays back sooner, in month 1, compared with month 3 for paid social.

A line chart of contribution after CAC per customer by month. Paid search turns positive in month 1 and paid social in month 3. Paid search stays ahead through month 6.
A line chart of contribution after CAC per customer by month. Paid search turns positive in month 1 and paid social in month 3. Paid search stays ahead through month 6.

Don't read the organic row as a free channel. Its acquisition cost is unmeasured here, and some of those customers may have seen an ad first.

Attribution doesn't tell you what the spend caused

This table shows what customers credited to each channel went on to be worth. It doesn't show how many of them you would've lost without the ads.

That's what a lift test is for. Google's Conversion Lift compares a group that saw ads with a group that didn't. Meta has its own lift reporting. Google calculates incremental CPA as spend divided by incremental conversions.

Say a test found that 75% of paid social's 400 customers were truly incremental. That's another made-up number. Incremental CAC becomes $18,000 / 300 = $60. Contribution after CAC goes from $13.59 to $58.59 - $60 = -$1.41 per incremental customer.

The channel goes from $13.59 after CAC to -$1.41 per incremental customer at month 6. That math assumes incremental customers are worth the same as everyone else in the cohort, which may not be true. Paid search should get the same test before you move budget, since branded search can capture people who were already coming. Comparing a tested channel with an untested one is its own mistake.

Lift tests have limits too. Google's help pages say Conversion Lift isn't available for every account, and a small channel may not have enough volume to read a result. When you can't run one, write down that the channel's number is attributed, not incremental.

Gotchas that change the answer

Younger cohorts look worse. A cohort that's three months old can't be compared with one that's twelve months old. Cut both off at the same age.

Late refunds change old months. A refund can land months after the order. Assign it to the original cohort and restate, and expect recent months to move.

Keep forecasts in their own column. Shopify's cohort report can project spend per customer, and you may build your own forecast. Label it. Don't blend it into what customers actually did.

Subscribers change the mix. If one channel brings in more subscribers, its curve may look different after month 6. Split subscribers from one-time buyers using a customer timeline before you blame the channel.

Small cohorts swing. A few large orders can flip a thin channel. Show customer counts next to every number.

Ratios hide payback. A 3:1 LTV to CAC rule of thumb doesn't tell you how long your cash is out. Show the month when contribution passes CAC.

I built this example to check the method and the arithmetic. It isn't client data, and your cost rules will be different. If you handle late refunds or subscriber mix in a cleaner way, I'd like to hear it. If you want to see how this fits with your other sources, the ecommerce data warehouse page lays out how I'd start.

Related

← All articles