Book a call
Tracking & attribution forensics

Reported ROAS is not real ROAS.

Your ad platform grades its own homework. It counts the orders it thinks it caused, and it never hears about the ones that got cancelled, returned or refunded. I reconcile what the platform claims against what your backend actually banked โ€” per ad โ€” then fix the tracking defects causing the gap.

Case: the platform said 7.39ร—. The real number was 4.61ร—.

A D2C supplements brand, ongoing retainer. Client identity withheld; every figure below comes from a live dashboard I run โ€” ad platform API, first-party tracker and the shop database, reconciled against each other.

Finding 1 โ€” the account-level gap

Two independent errors, stacking on top of each other

Matured 30-day cohort (orders given 5+ days to land a cancellation)

7.39ร—
ROAS the platform reported
4.61ร—
Real net ROAS
+60%
Overstatement

The gap was not one problem. It was two, and they multiply:

  • Attribution over-claim (~+22%) โ€” the platform counted orders it did not cause. Proven by joining platform-reported orders against first-party sessions, per ad.
  • Cancellations and returns (20.8%) โ€” the purchase pixel fires when the order is placed. Cancellations and RTOs happen days later and never reach the ad platform. So every ROAS it reports is structurally inflated.

Over that window, roughly a fifth of the "revenue" the platform was optimising against never actually arrived.

Finding 2 โ€” the expensive one

The per-ad ranking was inverted

Same cohort, joined ad-by-ad against shop-database order status

AdReported ROASReal net ROASCancel rateOver-claim
Ad A10.39ร—2.91ร—16.7%+257%
Ad B8.71ร—5.32ร—20.9%+64%
Ad C5.89ร—3.41ร—26.2%+73%
Ad D5.42ร—5.14ร—18.8%+5%
Ad E4.44ร—4.02ร—23.1%+10%
Ad F4.26ร—2.94ร—16.7%+45%

Ad A was the #1 ranked ad in the account by the platform's own numbers. Net of over-claim and cancellations, it was the worst performer in the account. Budget was flowing toward it.

Neither error is uniform. Over-claim ranged from +5% to +257%. Cancellation ranged from 16.7% to 26.2%. That matters more than it sounds: applying one blanket correction factor to the whole account โ€” which is what most "we adjust for returns" approaches do โ€” produces a different wrong answer. It has to be measured per ad.

The 7-day trap. In the platform's rolling 7-day view, a different ad showed 9.07ร— โ€” the best in the account that week. Its true cohort-based net ROAS was 2.94ร—. A small-spend ad's 7-day ROAS is noise, not signal. Scale on it and you scale your worst performer.
Finding 3 โ€” ruling out the boring explanation

"Your pixel is just broken" โ€” I tested that. It wasn't.

The obvious objection to any first-party-versus-platform gap is that the first-party data is under-counting. So that gets ruled out before any conclusion is drawn:

  • Click reconciliation: platform-reported link clicks versus first-party distinct sessions matched at 91โ€“105% on every single ad. The clicks landed. The tracker saw them.
  • Conversion-rate cross-check: the +257% ad converted at 1.04% while every other ad in the account ran 2.0โ€“3.3%. The order-count answer and the revenue answer agreed independently.
  • Capture rate measured, not assumed: the tracker's own capture rate was measured against the shop database and corrected for, rather than treated as 100%.

The clicks were real. The pixel was firing. The orders genuinely were not there.

Why most tracking audits are wrong

Including, for a while, mine. This is the part I would want to read if I were hiring someone.

97 sites, re-audited

I changed the method. All 97 results changed.

Nearly every free pixel-checker โ€” and a lot of paid audits โ€” fetch your page's raw HTML and pattern-match for fbq, gtag, G-, AW-. Tags injected at runtime by a tag manager do not appear in raw HTML. So the audit reports them missing, and the report lands in front of a client whose developer disproves it in two minutes.

I rebuilt the engine on a real headless browser โ€” mobile viewport, throttled connection, watching actual outbound network requests, on both the homepage and the conversion page. Then I re-ran the 97 sites I had already audited.

97 out of 97 findings changed. Sites flagged "no analytics" had analytics. Sites flagged "no conversion tracking" were tracking conversions.

Three rules are now enforced in code, because an audit that can only ever find fault is a sales script, not an audit:

  • Consent banner present โ†’ the "tag did not fire" finding is suppressed. The banner is legitimately blocking it. I cannot prove a defect, so I do not claim one.
  • A missing form name attribute is only reported when the form also has an action. JavaScript-submitted forms do not need it, and flagging them is noise.
  • Every report has a "what is already correct" section. If everything in your stack is fine, that is the finding.

Other things this has surfaced

Each of these is a real defect found in a live account, each one silently costing money before it was found.

A 13ร— cost-per-acquisition illusion

An education account reported a CPA that looked outstanding. The "conversions" were click proxies โ€” phone-button taps and chat-button clicks โ€” set as the primary conversion action. After switching to verified leads matched back by click ID and uploaded as offline conversions, true CPA was ~13.8ร— higher. Every report built on the old figure had been overstating performance by that factor.

Cloudflare silently destroying server-side match quality

A server-side conversion feed read cf-connecting-ip ahead of X-Forwarded-For. Behind a proxy that is always the sending server's own IP โ€” so every conversion was stamped with a datacenter IP instead of the buyer's. Result: junk geo data, a wrong client_ip_address sent to the conversions API, and 40 orders a day from one IP heading straight toward a repeat-IP fraud rule.

The same lead counted twice

A chat-button click was firing as a conversion and the resulting enquiry was being uploaded again as an offline conversion. The bidding algorithm was being trained on double-counted signal.

18 of 18 ads with no campaign parameters

An entire ad account running without dynamic URL parameters. Analytics could not attribute a single paid visit โ€” all of it landing as unassigned traffic, while the team debated which channel was working.

An alerting system crying wolf

A portfolio health check reported 9 of 9 properties in the red. All 9 were false: one was a conversion-code remap, one a firewall returning 403 to the checker, the rest simply low-volume days. Alerts nobody can trust get ignored โ€” including the real one.

A CSS bug that ate nine days of leads

On mobile, a flex-basis value became a height. The callback form input rendered as a ~340px circle. Desktop was fine, so nobody caught it. Nine days of mobile enquiries, gone, with no error anywhere to indicate it.

The engagement

Reported vs Real โ€” Ad Spend Truth Audit

What you get, in 10 working days

  • Platform-reported revenue and conversions reconciled against your backend, net of cancellations, returns, refunds and failed payments โ€” at account level and per ad.
  • A corrected per-ad ranking: which ads actually make money, ordered by net contribution rather than reported ROAS.
  • Every tracking defect causing the gap, with evidence โ€” real browser network traces, not HTML pattern-matching.
  • A prioritised fix plan, sequenced by money recovered per hour of work.
  • A walkthrough call. If your setup is already clean, you will be told that โ€” it happens, and it is a legitimate outcome.

Implementation is quoted separately once the audit tells us what actually needs building. Ongoing signal ownership โ€” acting as the tracking lead for an agency or an in-house team โ€” is a monthly retainer.

Working with agencies: a good chunk of this work is white-label, sitting behind an agency across many client accounts. If you are losing accounts to attribution arguments you cannot win, that is the conversation to have.

Find out what your ads are actually returning.

A 20-minute call. Bring one ad account and your backend revenue number for the same period. If I do not think there is a meaningful gap worth paying me to find, I will tell you on the call.

Remote engagements across the UAE, UK, Australia and North America. Based in India โ€” overlapping hours with Gulf and European mornings.