An attribution report is a set of arguments, not a set of facts. Every number in it is the output of a rule that decided which touch in a journey deserved the credit, applied to whatever journeys the tool was able to see.
So read it by asking three things of every row: which model produced it, what window it covers, and whether the tool could stitch one person’s sessions together at all.
Attribution reports rarely mislead on purpose. They mislead because a single number gets presented as the answer to a question nobody ever specified.
What is an attribution report actually telling you?
That a set of conversions happened, and that a rule assigned each one to a channel. The conversions are observed. The assignment is a choice. An attribution model is that choice written down — which touch in the journey gets the credit — and changing the rule moves every row in the table without a single sale changing.
Three things make up any report, and you need all three before a number means anything:
- The population. Which conversions were counted. A report counting form submissions and one counting paid invoices are not comparable, and both will call their column “conversions”.
- The window. How far back it looked for a touch to credit. A short window and a long one over the same journeys produce different winners, because only the long one sees the click that started things.
- The rule. Which touch in that window gets the credit — first, last, or some distribution across the middle.
A report that leaves any of the three implicit is asking you to trust its defaults: usually last-click, inside whatever window the tool shipped with, over the journeys it happened to stitch. Defensible choices — but not the truth, and not the choices your ad platform made.
Why do first-touch and last-touch disagree?
Because they answer different questions, and both answers are correct. First touch asks what introduced this customer. Last touch asks what closed them. A channel that creates demand and a channel that harvests it look completely different depending on which question you ask, and neither model is lying when they disagree.
The textbook shape: a cold paid social ad introduces someone who was not looking for you. Days later they search your brand name and buy. Last touch hands the sale to branded search, which now looks like your most efficient channel by a mile. Cut the paid social budget and branded search quietly dries up a month later.
First touch has the mirror-image failure: full credit to a top-of-funnel channel that introduces thousands of people who never convert, while the email that actually closed the sale shows nothing.

The takeaway is a diagnostic rather than a preference: the gap between the two models is the information. If first touch and last touch return the same picture across every channel, either your customers genuinely buy on a single touch — rare outside cheap impulse categories — or your tool is not connecting sessions into people, so every journey it sees is one session long.
The second case is far more common, and it is invisible unless you go looking. A single-touch report is not evidence that your customers are simple. It is usually evidence that your measurement is.
When the models do disagree, that disagreement is the input to the decision, not a problem to be solved by picking a favourite. Deciding which channel to scale is a question about what happens when you move budget, and one model cannot answer it alone.
Why doesn’t the assisted column add up to your total revenue?
Because assists are counted at full credit, once per assisting channel, rather than split into fractions. One order that touched three channels can appear as the full order value in three separate assist rows. That is deliberate, and it is why the assist table almost always carries a footnote saying it does not sum to the total.
The distinction to hold onto: the conversion table measures ownership, the assist table measures involvement. Different units — adding one to the other produces a number that corresponds to nothing.
What assists are good for is finding the channels last-touch reporting buries. A channel with a high share of assists and a low share of closes is in the path repeatedly and never gets the final click; cutting it because its attributed revenue looks small is the most common self-inflicted wound in paid media. Read the assist column as a ranking, never as a sum.
Why does your report disagree with your ad platform’s numbers?
Because the platform is reporting a different population, over a different window, under a different rule — and it can only count conversions it managed to match to a person it recognises. Four structural causes, none of them a bug:
Different windows. Each platform credits conversions inside its own attribution window, and the defaults differ between platforms. Two platforms can each legitimately claim the same sale.
View-through credit. Ad platforms may credit an impression that was never clicked. Your own report, built from your own site’s data, never saw that impression — which alone accounts for a large gap on upper-funnel campaigns.
Matching. An event the platform receives but cannot resolve to a user sits in its delivery numbers and not its attribution numbers. When you send conversions server-side through the Conversions API, how complete the identifier set is on each event is what the platform scores as event match quality — and an unmatched event is credited to no click, because the platform does not know whose click it was.
Deduplication. The same conversion can reach a platform twice, once from the browser and once from your server. Meta deduplicates those when both copies carry the same event name and the same event ID; when they do not, one sale is counted as two — and unevenly across channels.
Every ad platform is grading its own homework. That does not make its report worthless — it is the only view that includes impressions and the platform’s own identity graph — but it does make it unsuitable for comparing platforms against each other. Only your own report applies one rule to all of them.
What makes “Direct” the most misleading row in a report?
Direct is not a channel. It is the label a report puts on sessions where it could not work out a source. Read as traffic, it flatters you — all those people typing your domain from memory. Read correctly, it is the report’s own blind-spot meter, and it grows precisely when your tracking breaks.
What fills the bucket is almost all measurement failure rather than customer behaviour: blocked scripts, iframe-embedded forms, expired cookies, a journey split across two devices. The most common case of all is a conversion session that arrives bare because the visitor came back by typing the domain an hour after the ad click — which is why ad conversions show up as Direct far more often than any media buyer’s instinct says they should.
Two habits follow. Track Direct as a share over time, not an absolute — a step change after a site migration or a consent-banner change is a tracking regression, and anyone looking at one week will misread it as consumer behaviour. And treat a large Direct bucket as a ceiling on what any model in the report can tell you: you cannot model your way out of missing data.
What should you check before you trust a report?
Six things, in this order: the model, the window, the timezone, the currency, the deduplication and the identity layer. The first five you can read off the report header in a minute. The sixth decides whether anything above it is true.
- The model. Named on the report, or the report is unusable. If you cannot find it stated anywhere, assume last-click.
- The window. Compare like with like. Changing the window between two exports and calling the difference a trend is the easiest mistake here to make by accident.
- The timezone. Daily totals that disagree with an ad platform slightly, every day, in the same direction are a day-boundary problem, not a tracking problem.
- The currency. Mixed-currency revenue has to be normalised to something. Ask what rate, as of when — a report that silently converts at today’s rate restates last quarter every time you open it.
- The deduplication. One conversion, one row, no matter how many systems reported it. Webhooks get redelivered and pixels double-fire; if the report cannot show you it is idempotent, assume it is not.
- The identity layer. Whether the tool believes two sessions belong to the same human. If it does not, every model collapses to “last session” regardless of what the model selector says.
The first five you can fix with a settings change and some discipline. The sixth you cannot fix from a spreadsheet, and it does the most damage because it degrades silently — there is no error message for a report that quietly stopped connecting people.
It also has a floor no configuration moves: a report can only resolve people who identified themselves somewhere. Someone who typed an email into your form and left without submitting is an identifiable person — a partial lead — but only if something captured that field before the page closed. If nothing did, there is nothing to join on later, and their purchase lands in Direct forever.
How does PartialLeads report attribution?
By showing the disagreement rather than hiding it. The Attribution report puts three models side by side — First Touch, Last Touch, and PartialLeads Resolved — each with its own purchase count and its own per-channel split. The resolved model can show a higher purchase count than the other two, because it recovers purchases a raw first- or last-click view loses entirely.
Above them sits a KPI strip: attributed revenue, attributed purchases, time to purchase as median and average, match rate, and recovered revenue — each compared against the prior period. Period, timezone and currency are explicit selectors rather than buried defaults, and multi-currency revenue carries a footnote naming the rate source and date. The display layer resolves on last-non-direct with a 28-day decay window, chosen to line up with the window advertisers already read in their ad platform.
The part that answers the Direct problem is Recovered Attribution, itemised by mechanism rather than given as one lump: first touch re-linked to a lost ad click; sessions with no campaign parameters reclassified to a real channel by the session classifier, with per-channel counts; and revenue from matched buyers whose session never finished a form. The reclassified bucket is typically the largest — traffic that would otherwise have read as Direct.

The Channels table carries a First / Last / Resolved toggle, so the ranking moves under each rule in front of you; paid channels expand to their campaigns, with average lag, visitor-to-lead rate and ROAS columns. Conversion Paths & Assists shows path sequences with counts, a touches-per-purchase distribution and a days-to-purchase distribution, plus an assisting-channels table labelled explicitly as full-credit and not summing to the total. Time to Purchase plots the first-touch series against the last-touch series, keeping journey length and decision length separable. And Revenue by Match Quality splits net revenue into partial-captured, submit-completed and unmatched.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| One number presented as the answer | First, Last and Resolved models rendered side by side | Attribution report, model cards |
| Conversions that lose their source entirely | Resolved model over the identity cluster, own purchase count | Resolved model card vs first/last |
| Sessions with no campaign parameters read as Direct | Session classifier reclassification, itemised per channel | Recovered Attribution, reclassified bucket |
| A sale weeks after the ad click | First touch stored on the person, not the session | Time to Purchase, first vs last series |
| Assisting channels buried by last-click | Conversion paths and assisting-channels table | Conversion Paths & Assists |
| Buyers whose form was never submitted | Partial capture before submit, matched to the purchase | Revenue by Match Quality, partial-captured wedge |
| Reports that disagree with the ad platform | Explicit period, timezone and currency selectors | Report header chips, ECB rate footnote |
| Refunds inflating attributed revenue | Refund rows netted against paid | Purchases ledger, net revenue |
| A redelivered webhook counted twice | Deterministic event ID with unique dedup tables | CAPI activity log, one row per event |
The honest constraints, because a report that hides its own limits is the thing this article argues against. The resolved model is built from deterministic joins and bounded inheritance, not probabilistic modelling — it will not invent a source where no identifier existed. Conversion metrics can show a “warming up” state while visitor figures are still computing, rather than a confident wrong number. The assisting-channels table does not sum to the total, by design. And PartialLeads controls what gets dispatched to an ad platform and how complete it is, never whether that platform can resolve the person to an account — that depends on its graph, consent and opt-out status. No vendor owns that half.
Tell us what's broken. We'll fix your tracking — free.
Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.
Sources
- Meta for Developers — Conversions API overview: https://developers.facebook.com/docs/marketing-api/conversions-api
- Meta for Developers — Deduplicate Pixel and Conversions API events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events
- Meta for Developers — Conversions API best practices: https://developers.facebook.com/docs/marketing-api/conversions-api/best-practices