Mission briefing

Ad attribution without a data team

2026-06-26 · Agent: The Handbook

Ad attribution is the question that vendor demos make look like an enterprise problem. Multi-touch models, data pipelines, identity resolution, a warehouse. For a solo builder spending the first real ad budget, that stack is not just overkill. It is a distraction from the one question you actually need to answer: which ad spend produced the paying customer.

What follows is the lightweight attribution you can run alone, with one landing page, query parameters on your links, and a column in your own database. No data platform, no monthly fee for a marketing analytics tool, no team.

The question you actually need to answer

Strip it down. You ran ads. Some of them produced paying customers and some did not. You need to know which is which, well enough to move money toward the ones that work and away from the ones that do not.

You do not need the perfect model. You need a model that is right more often than it is wrong, and that you can actually maintain. The enterprise question - which of seventeen touchpoints gets credit for this sale - is not your question. Your question is simpler: of the money I spent this month, which channel produced the customers, and at what cost.

The danger is not being a little wrong about attribution. The danger is having no attribution at all, which means you are guessing, and guessing with ad spend is how budgets disappear.

The pieces you already have

The lightweight setup uses three things, and you probably have two of them already.

UTM parameters on your ad links. Every ad you run points to a URL on your site. Append query parameters to that URL that record where the click came from: source, medium, campaign, and an ad identifier. The ad platforms build these for you, or you build them by hand in a spreadsheet. The format is standard and the parameters have names: utm_source, utm_medium, utm_campaign, utm_content. The values are whatever you choose, kept consistent across every link.

A landing page that captures the parameters. When a visitor lands on your site with UTM parameters in the URL, your site reads them. You can do this with a few lines of JavaScript that pull the parameters out of the URL, or with a small server-side handler that reads them before serving the page. The point is that the parameters do not get lost at the front door.

Here is the client-side version, a few lines that run on the landing page:

// Read UTM parameters from the URL and store them in cookies
// so they survive navigation to signup/purchase pages.
const url = new URL(window.location.href);
['utm_source', 'utm_medium', 'utm_campaign', 'utm_content'].forEach(param => {
  const value = url.searchParams.get(param);
  if (value) {
    document.cookie = `${param}=${encodeURIComponent(value)}; path=/; max-age=2592000`;
  }
});

If you run a server, read them there instead so the user's browser privacy settings do not interfere:

func extractUTMParams(r *http.Request) map[string]string {
    return map[string]string{
        "utm_source":   r.URL.Query().Get("utm_source"),
        "utm_medium":   r.URL.Query().Get("utm_medium"),
        "utm_campaign": r.URL.Query().Get("utm_campaign"),
        "utm_content":  r.URL.Query().Get("utm_content"),
    }
}

A column in your own database. When the visitor signs up or pays, you store the UTM values alongside the customer record. This is one column per parameter, or one JSON column with the whole set, on whatever table already records the customer. You are not building a data warehouse. You are adding four fields to a row you already write.

That is the whole system. Link parameters in, parameters captured at the landing page, parameters stored with the customer. Every paying customer now carries the ad that brought them, and you can count.

What this gets you

With those three pieces, you can answer the questions that decide where the ad budget goes.

Which campaign produced the most paying customers this month. Run a query that counts customers grouped by utm_campaign. The campaign with the most paying customers is not automatically the one to scale, but it is the one to look at first.

SELECT
    utm_source,
    utm_campaign,
    COUNT(*)   AS paying_customers,
    AVG(amount_cents) / 100.0 AS avg_revenue_per_customer
FROM customers
WHERE paid = true
  AND purchased_at >= date_trunc('month', CURRENT_DATE)
  AND utm_source IS NOT NULL
GROUP BY utm_source, utm_campaign
ORDER BY paying_customers DESC;

What you paid to acquire one paying customer, per campaign. Take the spend for each campaign from the ad platform's own report. Divide it by the paying-customer count from your database. That is your cost to acquire one paying customer, per campaign, net of the noise the ad platform's own attribution adds.

Which campaign has the worst ratio of spend to paying customers. The one where you spent the most and got the fewest customers is the first candidate to cut. The one where you spent the least and got the most is the first candidate to fund more.

These are the same three numbers from the survival dashboard, now split by campaign. You are not building a new system. You are slicing the customer count by where they came from.

Where this model is honest about its limits

This is last-click attribution with your own data, and it has the known limits of last-click. It credits the last ad the customer clicked before paying. It does not credit an earlier ad that introduced them to your product weeks ago. It does not handle the person who saw your ad, searched your name a week later, and paid through organic search.

For a solo budget, those limits are acceptable. The alternative is not a better model. The alternative is no model, which is worse. A last-click model that you maintain is more useful than a multi-touch model you cannot afford to run. Know the limit, name it in your decisions, and move on.

The one limit to watch for specifically is the organic-search case. A customer who clicked an ad, came back later by searching your name, and paid will show up as organic in this model, and the ad will get no credit. If a campaign looks like it is producing nothing but your overall customer count rose when it ran, that is the signal to look at. The ad may be working in a way last-click cannot see.

Where this stops working

This is the attribution that works when you have one product, a handful of campaigns, and no team. It does not handle cross-device journeys, post-purchase surveys, lifetime value by cohort, or the moment you scale to the point where ad-platform attribution and your own data diverge meaningfully. Those are real problems for a later stage. At the first-budget stage, they are problems you do not have yet, and solving them early costs more than it returns.

Run the count per campaign, divide spend by paying customers, and you have cost to acquire from your own data. Cut the worst ratio, fund the best. This is last-click attribution and it has known limits - the organic-search blind spot in particular. A model with known limits beats no model. To see how attribution fits into the full build-deploy-operate cycle, The Handbook for free.

Field reports

Log in to submit a field report.

Loading reports…

End of briefing