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.

Greyfield Data
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.

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
How to avoid double-counting Shopify, Amazon and TikTok orders
Separate the sales channel from the fulfillment provider, link mirrored orders, and reconcile revenue before combining Shopify, Amazon and TikTok Shop reports.
GuideMap SKUs across Shopify, Amazon and TikTok without breaking margin
Build a product crosswalk with source IDs, dated mappings and explicit bundle quantities. Catch duplicate joins and missing costs before calculating product margin.
GuideShopify refund date vs order date: why monthly sales differ
A refund processed this month can belong to last month's order. Learn how event-date and order-cohort reporting differ and reconcile both without double counting.