Meta says 23, the backend says 4: how I reconcile ad platforms with real revenue

A working checklist for putting Meta Ads, Google Ads and MMP numbers next to backend data, with the tracking faults I have actually found.

A tall empty glass column beside a short solid column with a brass top

On the first full day of clean tracking, Meta claimed 23 first-time depositors on one brand where the backend saw 4.

That is one day, so it is a flag, not a verdict. But it is the kind of gap that decides where the next dollar goes, and most teams never look at it because the platform dashboard is the easiest number to open.

This is the process I use to put platform numbers and backend numbers side by side. It works the same for app installs, purchases, leads and deposits.

Why the numbers never match

Part of the gap is legitimate and part of it is broken tracking. Separate the two before you argue about either.

  • Attribution windows. The platform counts a conversion inside its own window, often including people who saw an ad and never clicked. Your backend has no idea the ad existed.
  • Modeled conversions. Platforms estimate conversions they can't observe directly, for example after users opt out of tracking on iOS. Those estimates appear in the columns, never in your database.
  • Double counting. A browser pixel and a server event for the same action, both counted.
  • Time zone and currency. The ad account closes the day at one hour and your backend at another. Revenue arrives in one currency and gets reported in another.
  • Definitions. The platform's purchase may be your checkout started.
Diagram comparing 23 first-time depositors reported by the platform with 4 recorded by the backendMeta reported 23 first-time depositors on one brand where the backend recorded 4, on the first full day of clean tracking. Between the two sit five layers that make platform and backend disagree: attribution windows, modeled conversions, double counting, time zone and currency, and definitions. One day is a flag, not a trend.META REPORTED23first-time depositors12345BACKEND RECORDED4first-time depositors1Attribution windows2Modeled conversions3Double counting4Time zone and currency5DefinitionsFirst full day of clean tracking. One day is a flag, not a trend.
Where the gap comes from: the five usual reasons a platform and a backend disagree.

Step 1. One definition per money event

Write down, for each platform, which event is the money event and which backend record it corresponds to. One money event per app per network. If two events feed the same optimization, the platform will optimize toward the cheaper one.

Then check what fires and for whom. I have caught two setups before launch that would have corrupted bidding:

Conversion events firing for over 99% of users, and a purchase action that defaulted to one euro when no value arrived.

A conversion that every user triggers is not a signal. A default value is worse, because it looks like data.

Step 2. Pixel and Conversions API deduplication

If you send the same purchase from the browser and from your server, Meta needs the same event name and the same event ID on both to count it once. Check in Events Manager that browser and server events are being deduplicated, not added together.

Then check the opposite failure: server events that never arrive. Sending both is the right setup. Sending both without a shared ID doubles your conversions, and losing the server side quietly halves your signal.

Step 3. Every layer between server and platform

I traced under-firing Conversions API events to a destination trigger that filtered 99.35% of server events over 48 hours. Removing it restored delivery the same day, and the fix went to the sister brands.

Nobody had set out to block conversions. A filter written for one purpose matched almost everything. Look at every layer that can drop or change an event: tag manager triggers, server-side containers, destination filters in your customer data pipeline, consent logic and the event setup inside the platform itself.

The method is simple. Pick a 48-hour window and count the same event at every hop, from your backend to the platform. The hop where the count falls is where the problem lives.

Step 4. Count each conversion once in Google Ads

In Google Ads, a conversion action set as primary feeds bidding. If the same deposit or purchase is imported twice, or counted every time when you meant once, Smart Bidding chases a number that doesn't exist.

I cleaned the bidding signal across a group's Google Ads accounts: removed deposit conversions counted more than once and left one primary first-deposit action per account.

Keep one primary action per goal and set the rest as secondary, so you can still see them without bidding on them.

Step 5. For apps, compare at postback level

On mobile, the MMP sits between the ad network and your backend. Compare three numbers for the same cohort: what the network reports, what the MMP attributes to that network, and what your backend records for those users. When two of the three disagree, the postbacks tell you which one is wrong.

I have found and priced an app-campaign attribution gap this way, proven at postback level. On iOS, add SKAN or AdAttributionKit conversion values to the comparison and expect delays and coarser data.

Step 6. Build the reconciliation table

One row per day and per campaign:

Columns of the reconciliation table
ColumnWhat goes in it
Platform conversionsAs reported, with the attribution setting written in the header
Backend conversions, matchedConversions you can tie to the campaign by click ID or UTM
Backend totalEvery conversion of that type that day, matched or not
RatioPlatform divided by matched backend
Revenue and paybackBackend revenue for the cohort, against spend

Read it over 7 and 28 days, never over one day. The ratio won't be 1. What you want is a ratio that is stable and that you can explain. A sudden change means something broke, either in the tracking or in the traffic.

Step 7. Decide on the backend number

Once the table exists, budget decisions change. Three examples from my own work:

I killed the campaign absorbing about 60% of spend for under 4% of registrations and rebuilt it on backend deposit data.

I wrote the kill rule before launch. We killed the search offer test at zero new depositors while the platform rated it the account's best cost per conversion.

On a DSP campaign for a gaming app, CPI fell from $4.74 to $1.66 and I didn't call it a win, because day-one retention fell from 15.1% to 9.9%.

The platform number is a lead. The backend is the verdict.

Checklist

  • One money event per platform, mapped to one backend record
  • No conversion that fires for nearly every user, and no default values
  • Pixel and Conversions API deduplicated with the same event name and event ID
  • Counts compared at every hop between your server and the platform
  • One primary conversion action per goal in Google Ads
  • Network, MMP and backend compared for the same cohort on apps
  • A daily reconciliation table, read over 7 and 28 days
  • Budget decisions taken on backend revenue and payback

Want these numbers worked out for your account? The 30-minute diagnostic is free.