Marketing & demand

B2B Revenue Attribution: Connecting Campaigns to Gross Profit

Every marketing platform will tell you what it produced. None of them know what it was worth, because the number that decides it — gross profit, months later, net of returns — lives in the ERP. Here is how to connect the two, and where the honest limits are.

Revaion · Written for finance teams running NetSuite · 9 min read
Definition

B2B revenue attribution connects marketing activity to the revenue and gross profit it eventually produced — not to clicks, form fills or MQLs. In B2B that means joining marketing data to order and margin data in the ERP, because that is the only place the money is actually recorded.

There is a familiar meeting in most B2B businesses. Marketing presents a deck showing traffic up, conversions up, cost per lead down. Finance looks at gross profit, which has not moved. Neither party is lying and neither can reconcile the other's numbers, because the two are measuring different things in different systems and the join between them has never been built.

The uncomfortable part is that marketing's numbers are usually correct and simply not decision-useful. A conversion is a proxy. It was invented because the real outcome was unmeasurable, and in B2B it remains a proxy for something that happens months later, in a different system, to a different set of people.

Why B2B breaks the standard attribution model

The attribution machinery most platforms provide was built for ecommerce, where someone clicks an ad and buys twenty minutes later in the same browser session. Four things about B2B break it:

The consequence

Channels get judged on a metric that is measured early, in the wrong system, on the wrong entity (a session rather than a company), and stops counting long before the value arrives. It is not surprising the answer is wrong; it would be surprising if it were right.

The bridge is the customer record, not the click

The instinct is to try harder at tracking — more pixels, longer windows, better identity resolution. In B2B this mostly produces expensive noise.

The productive move is to change the unit of analysis. Stop trying to attribute orders to sessions, and start attributing customers to sources. A company is a stable entity that exists in both systems. It has one record in your ERP, it accumulates orders and gross profit over years, and it only needs to be connected to its origin once.

That reframing makes the problem tractable, because you no longer need to observe a journey. You need one durable field: where this customer came from.

Level 1 — stamp the source and never lose it

Everything downstream depends on this and it is where most implementations quietly fail. The requirement is that every customer record in NetSuite carries an immutable acquisition source, set once at creation and never overwritten.

Three rules make it work:

  1. Capture at first contact, not at order. The source belongs to the enquiry that created the relationship. By the time an order is placed the referrer is your own website and the information is gone.
  2. Never overwrite it. If a customer acquired through referral in 2024 later clicks a paid ad, that click is not an acquisition. Overwriting is how paid search ends up taking credit for customers it did not win — one of the most common and most expensive errors in this whole exercise.
  3. Keep it coarse. Eight to twelve sources, not two hundred campaign IDs. You need categories you can make budget decisions about. Campaign-level detail belongs in the marketing platform.

NetSuite's standard leadsource field on the customer record is usually adequate. If it is already used for something else, add a custom field — but add it before you need the history, because this data cannot be reconstructed retrospectively.

Query 1 — new vs existing customer revenue by month

Before attributing anything to a channel, establish how much of your revenue is actually new business. In most established B2B businesses the honest answer is 5–15%, and that single number reframes what marketing can plausibly be held responsible for.

-- New vs existing customer revenue and gross profit, by month.
WITH first_order AS (
  SELECT t.entity AS cust, MIN(t.trandate) AS first_order_date
  FROM transaction t
  WHERE t.type = 'SalesOrd' AND t.voided = 'F'
  GROUP BY t.entity
),
monthly AS (
  SELECT
    TO_CHAR(t.trandate, 'YYYY-MM')                  AS month,
    t.entity                                        AS cust,
    SUM(tl.rate * ABS(tl.quantity))               AS revenue,
    SUM((tl.rate * ABS(tl.quantity)) - tl.costestimate) AS gp
  FROM transaction t
  JOIN transactionline tl ON tl.transaction = t.id
  WHERE t.type = 'SalesOrd' AND t.voided = 'F'
    AND tl.mainline = 'F' AND tl.taxline = 'F'
    AND t.trandate >= ADD_MONTHS(CURRENT_DATE, -24)
  GROUP BY TO_CHAR(t.trandate, 'YYYY-MM'), t.entity
)
SELECT
  m.month,
  COUNT(DISTINCT CASE WHEN TO_CHAR(f.first_order_date, 'YYYY-MM') = m.month
                      THEN m.cust END)                AS new_customers,
  ROUND(SUM(CASE WHEN TO_CHAR(f.first_order_date, 'YYYY-MM') = m.month
                 THEN m.revenue ELSE 0 END), 0)      AS new_revenue,
  ROUND(SUM(CASE WHEN TO_CHAR(f.first_order_date, 'YYYY-MM') <> m.month
                 THEN m.revenue ELSE 0 END), 0)      AS existing_revenue,
  ROUND(SUM(CASE WHEN TO_CHAR(f.first_order_date, 'YYYY-MM') = m.month
                 THEN m.gp ELSE 0 END), 0)           AS new_gp,
  ROUND(SUM(m.revenue), 0)                       AS total_revenue,
  ROUND(SUM(CASE WHEN TO_CHAR(f.first_order_date, 'YYYY-MM') = m.month
                 THEN m.revenue ELSE 0 END)
        / NULLIF(SUM(m.revenue), 0) * 100, 1)      AS pct_from_new
FROM monthly m
JOIN first_order f ON f.cust = m.cust
GROUP BY m.month
ORDER BY m.month

"New" here means first-ever order, which is the right definition for acquisition. If you also want reactivated dormant accounts, add a second flag for customers whose previous order was more than eighteen months earlier — they behave like new business commercially but shouldn't be charged to acquisition spend.

Query 2 — cohort performance by acquisition source

This is the one that changes budgets. Group customers by the month they first ordered and the source they came from, then measure what each cohort went on to be worth.

-- Customer cohorts by acquisition month and lead source,
-- measured on lifetime gross profit rather than first order.
WITH first_order AS (
  SELECT t.entity AS cust, MIN(t.trandate) AS first_order_date
  FROM transaction t
  WHERE t.type = 'SalesOrd' AND t.voided = 'F'
  GROUP BY t.entity
),
perf AS (
  SELECT
    t.entity                                        AS cust,
    COUNT(DISTINCT t.id)                            AS orders,
    SUM(tl.rate * ABS(tl.quantity))               AS revenue,
    SUM((tl.rate * ABS(tl.quantity)) - tl.costestimate) AS gross_profit
  FROM transaction t
  JOIN transactionline tl ON tl.transaction = t.id
  WHERE t.type = 'SalesOrd' AND t.voided = 'F'
    AND tl.mainline = 'F' AND tl.taxline = 'F'
  GROUP BY t.entity
)
SELECT
  TO_CHAR(f.first_order_date, 'YYYY-MM')           AS cohort_month,
  NVL(BUILTIN.DF(c.leadsource), 'Unattributed')   AS source,
  COUNT(*)                                          AS customers,
  ROUND(SUM(p.revenue), 0)                        AS lifetime_revenue,
  ROUND(SUM(p.gross_profit), 0)                   AS lifetime_gp,
  ROUND(SUM(p.gross_profit) / NULLIF(COUNT(*), 0), 0) AS gp_per_customer,
  ROUND(SUM(p.gross_profit) / NULLIF(SUM(p.revenue), 0) * 100, 1) AS gm_pct,
  ROUND(AVG(p.orders), 1)                        AS avg_orders,
  ROUND(AVG(CURRENT_DATE - f.first_order_date) / 30, 1) AS avg_months_held
FROM first_order f
JOIN perf p     ON p.cust = f.cust
JOIN customer c ON c.id   = f.cust
WHERE f.first_order_date >= ADD_MONTHS(CURRENT_DATE, -36)
GROUP BY TO_CHAR(f.first_order_date, 'YYYY-MM'),
         NVL(BUILTIN.DF(c.leadsource), 'Unattributed')
ORDER BY cohort_month, lifetime_gp DESC

Drop cohort_month from the SELECT and GROUP BY to get straight source-level totals. Keep it in when you want to see whether a channel's quality is improving or degrading over time — which is usually the more interesting question.

Watch the Unattributed row. If it is more than about a third of your customers, the source stamping isn't working and every other number here is unreliable. Fix that before drawing conclusions.

Why gross profit reverses the ranking

This is the part that justifies the whole exercise. Two channels, same twelve-month spend:

Paid searchReferral / word of mouth
Spend£60,000£60,000
New customers12438
Cost per customer£484£1,579
Lifetime revenue£1,240,000£988,000
Average gross margin19.2%31.4%
Lifetime gross profit£238,000£310,000
GP per customer£1,919£8,158
Return on spend (GP)4.0×5.2×

On every metric a marketing platform reports — leads, customers, cost per acquisition, revenue — paid search wins comfortably and by a wide margin. On gross profit it loses.

The mechanism is not mysterious. Search traffic arrives comparing prices, converts on the cheapest option, and buys at a discount. Referral arrives pre-sold, negotiates less, and stays longer. Those are real and persistent differences in customer quality, and revenue-based measurement is structurally blind to all of them.

The follow-on question

Once you can see this, the interesting move is usually not "cut paid search". It's "why does paid search convert at 19% margin?" Often the answer is a discount code in the ad copy, or a landing page leading on price — which makes it a fixable pricing problem rather than a channel problem. See finding revenue leakage in NetSuite for the margin side of that.

Turning it into CAC and payback

NetSuite holds the return; your ad platforms and payroll hold the investment. The join is manual and that is fine — it's a monthly spreadsheet, not an integration project.

Four numbers per source:

MetricCalculationWhat it tells you
CACTotal channel cost ÷ new customers acquiredWhat buying a customer here costs
GP per customerFrom Query 2What one is worth, so far
GP paybackCAC ÷ (GP per customer ÷ months held)Months to recover the spend — the cash-flow constraint
GP:CAC ratioGP per customer ÷ CACWhether the channel is worth scaling

Include people cost in channel cost. A channel that needs constant manual work is more expensive than its media spend suggests, and excluding the labour is how businesses over-invest in channels that quietly consume a full-time role.

Payback deserves particular attention in a distribution business, because acquisition spend and inventory compete for the same working capital. A channel with excellent lifetime economics and a fourteen-month payback may still be the wrong one to scale this year. That trade-off connects directly to how quickly your customers pay.

The honest limits

Four things this method does not do, worth stating before someone else does:

It won't give you multi-touch attribution. It assigns a customer to one origin. In reality they saw a LinkedIn post, met you at a trade show, and were referred by an existing customer. Single-touch is a simplification — but it is a transparent one, whereas multi-touch models in B2B allocate credit using assumptions dressed as evidence. Being visibly approximate beats being invisibly wrong.

It can't measure brand or demand creation. The activity that made someone type your name into Google six months later gets recorded as "direct". Direct is not a channel; it's a bucket of things that worked earlier and can't be traced. Don't let anyone conclude that brand spend does nothing because it doesn't appear here.

Young cohorts look worse than they are. A customer acquired last month has one order. One acquired three years ago has forty. Only compare cohorts at the same age, or you will conclude that every channel has recently stopped working.

It depends entirely on the source field. All of it. If nobody stamps the source at first contact, or if someone helpfully "tidies up" the field, the entire analysis becomes fiction. That's a process discipline problem, not a data problem, and it's the one worth investing in first.

None of these limits stop the method being far more useful than what it replaces. Going from "cost per lead is down 12%" to "referral produces customers worth 4× more gross profit than paid search" is a change in the quality of the conversation, not just the precision of the number.

Marketing measured in gross profit, not clicks

Revaion Demand connects campaign performance across search, web, email, social and paid media to the customers, orders and gross profit already inside NetSuite — one weekly answer, not five dashboards.

See how Revaion Demand works →

Connect your campaigns to gross profit.

Book a 30-minute discovery call. We'll walk through how Revaion attributes gross profit — not just revenue — back to source, and what that changes about your spend.

Or email [email protected]

We use your email only to reply to this enquiry. See our privacy policy.