App revenue tracking almost never fails loudly. No error, no alert, no red banner in a dashboard. It fails silently: an event keeps firing while its value quietly reads zero, one pipeline keeps working while its twin drops revenue, and Smart Bidding keeps spending against numbers that stopped meaning anything.

I know because I shipped every failure in this post. They all come from one real stack I run for a Swiss booking marketplace: a React Native app, Firebase direct to GA4 and Google Ads, Adjust via a customer data platform to Meta.

This is the second post in a three-part series on app tracking. Part one covered the dual-stack architecture and who feeds what: why one tool is not enough, and how the Firebase and MMP pipelines run in parallel. This one is the failure catalogue: five ways that stack breaks without telling you, and the monitoring that would have caught each one earlier than I did. Part three covers the iOS attribution gap that hides app conversions from Google Ads.

App tracking architecture: app events split into two parallel pipelines — Firebase's direct SDK to GA4 and Google Ads, and a CDP to the Adjust MMP and on to Meta.

TL;DR

  • The five silent failures: reading the wrong GA4 metric, events firing without value parameters, one of two pipelines dropping revenue, flat default conversion values, and dead events nobody notices.
  • None of them produce an error. All of them corrupt bidding, because the platforms optimize toward whatever numbers arrive.
  • Each failure has a cheap detection: a weekly reconciliation of Firebase vs MMP vs backend revenue, a per-event freshness check, and a value-coverage check in both pipelines.
  • The uncomfortable rule: an event name arriving is not evidence that its value arrived. Verify values separately, per pipeline.

Failure 1: the metric that lies by design

The first time we checked app revenue in GA4, the answer was 0.00. Every booking event, zero revenue. We assumed the implementation was broken.

Half of it was something else: GA4 has two revenue metrics that behave differently. “Total revenue” only counts standard event names like purchase or in_app_purchase. Our booking events used custom names, and custom-named events report their money under “Event value”, not “Total revenue”. The revenue was arriving; we were reading the metric that is defined to ignore it.

This is not a bug, it is a naming rule, and it is documented. But nobody reads the definition of a metric that shows a plausible zero. We lost days to it.

The check: for custom-named conversion events, build reports on “Event value”. Reserve “Total revenue” for standard ecommerce event names. Write this into your dashboard spec so the next person does not rediscover it.

Failure 2: the events that really did send nothing

The other half of that zero was real. At audit time, the app was firing over 200 distinct events, and the money events among them carried no value parameter at all. Bookings fired as bare signals: the platforms knew something happened, but not what it was worth.

The consequence is worse than missing reporting. Google and Meta bid on values. An event stream without values forces volume bidding: every conversion counts as one, so the algorithm hunts the cheapest conversions it can find, and a high-value booking is worth exactly as much to it as a trivial one. I covered the web version of this in the dynamic conversion values post; in apps the mechanics are identical, just easier to get wrong twice, because there are two pipelines.

The same booking_completed event in two states: received with no value and no currency, counting as one conversion with no revenue signal, and received with a value of 80 EUR, usable for value-based bidding.

The check: for each money event, in each pipeline, confirm the value and currency parameters are present on live traffic. Event coverage and value coverage are different audits.

Failure 3: two pipelines, one silently broken

This is the expensive one. After engineering shipped the value fix, Firebase revenue worked: real booking values, flowing to GA4 and Google Ads. The Adjust (MMP) pipeline received the same events, from the same user actions, on the same days, and captured roughly an eighth of the revenue Firebase saw. Then it flatlined to zero while Firebase kept recording money.

Nothing errored. The cause lived in the second pipeline’s plumbing: the app sent events to the MMP through a customer data platform, and the revenue call had failure modes the direct Firebase call did not. A price helper returning null for some booking types made the revenue NaN. A revenue field that arrived as a string instead of a number. A currency label that was not exactly the ISO code the SDK expected. Each of these makes an MMP SDK silently skip attaching revenue: the event still arrives, looking healthy, worth nothing.

The same events in two pipelines: Firebase to GA4 and Google Ads records 80 EUR of revenue, while the CDP to Adjust and Meta records 10 EUR — an 8x gap caused by a price helper returning NaN, a value sent as a string, or a currency label that is not the ISO code.

Because the MMP was our only conversion path to Meta, this meant Meta was optimizing on empty values while Google optimized on real ones, and the difference was invisible in either platform alone.

The check: reconcile revenue across Firebase, the MMP, and your backend weekly, with a tolerance you set in advance. The three will never match exactly. But an 8x gap is not an attribution nuance, it is a dropped-revenue bug, and only a cross-source comparison surfaces it. When you find it, debug with a log line before the SDK call: the type and value of revenue and currency. NaN, undefined, "NaN" as a string, or a non-ISO currency label is your bug.

Failure 4: the flat default value

While values were broken, the Google Ads conversion actions fell back to a default value of 1.00. The real average booking value was roughly 11x higher.

A wrong default is worse than it sounds, because it does not look wrong. Conversions flow, dashboards fill, ROAS math runs. But the bidding algorithm is optimizing against numbers an order of magnitude off, and every automated budget decision inherits the distortion. When we fixed the pipeline, we also raised the default so that any future gap would at least fail toward realistic numbers instead of toward 1.00.

The check: open every app conversion action in Google Ads and read three settings: the value (default vs dynamic), whether the action is Primary or Secondary, and the attribution window. Defaults are set once, usually during a setup sprint, and then survive unexamined for years.

Failure 5: the event that died and nobody noticed

One conversion event, a supply-side signup we cared about, stopped arriving entirely. By the time an audit caught it, it had been dead for more than two months. No alert existed, because event pipelines do not alert; they just stop.

Every tracking setup decays. App releases rename things, SDK updates change behavior, a refactor drops a call. The question is not whether an event will die, but how long it stays dead before anyone notices. Two months of a missing conversion signal is two months of a platform mis-learning what your users do.

The check: a per-event freshness alarm. The simplest version is a weekly look at days-since-last-event for every conversion event, in both pipelines. Anything over a few days on an event that should fire daily is an incident, not a curiosity.

The monitoring checklist

Everything above compresses into five recurring checks. They take under an hour a week on a small stack:

  1. Value coverage: every money event carries value + currency, in Firebase and in the MMP, on live traffic.
  2. Cross-source reconciliation: Firebase vs MMP vs backend revenue, weekly, against a pre-agreed tolerance.
  3. Event freshness: days-since-last-fire per conversion event, both pipelines. Dead events are incidents.
  4. Platform settings: conversion action values, Primary/Secondary status, and attribution windows in Google Ads; event and value receipt in Meta Events Manager.
  5. Metric definitions: dashboards read “Event value” for custom events, and every chart states which source it draws from.
Three essential checks: value coverage — value and currency on every money event, in both pipelines; revenue reconciliation — Firebase against MMP against the backend, weekly; event freshness — an alert when key events stop arriving for 24 to 72 hours.

None of this requires tooling budget. It requires deciding that tracking is a production system, monitored like one.

FAQ

How much revenue discrepancy between sources is normal?

Firebase, an MMP, and a backend measure different things (attribution logic, consent, timing), so gaps of 10-30% on specific slices are common. Set your own tolerance from a few clean weeks. Multiples, like the 8x gap we hit, are always bugs.

Why don’t my Firebase and Adjust revenue numbers match?

Some gap is normal, but a multiple is a bug, not an attribution nuance. When we hit it, Firebase reported roughly 8x the revenue Adjust captured, because the Adjust pipeline was silently dropping malformed values (NaN, a string where a number was expected, or a non-ISO currency label) while the direct Firebase call attached them correctly. See Failure 3: reconcile the two against your backend weekly, and if one reads a fraction of the other, log the type and value of revenue and currency right before the SDK call to find where it drops.

My GA4 shows 0.00 revenue on app events. Broken or not?

Check the metric first: custom-named events report under “Event value”, not “Total revenue”. If “Event value” is also zero, then the value parameter is genuinely missing and you have failure 2.

Why did the MMP drop revenue without any error?

MMP SDKs validate the revenue call and skip it silently when the input is malformed: NaN, a string where a number is expected, or an unexpected currency label. The event itself still arrives, which is what makes it invisible.

Should the default conversion value just be zero?

No. Set it near your real average so that a future pipeline gap degrades gracefully. A default of 1.00 on a high-value product is a standing invitation for bidding distortion.

Who should own these checks?

Whoever owns paid performance, not engineering. Engineering fixes pipelines; marketing suffers silently when they break. The checklist exists so the person spending the budget notices first.

Key takeaways

  1. App revenue tracking fails silently by design: SDKs skip malformed values without erroring, and platforms happily bid on whatever arrives.
  2. An event arriving is not evidence its value arrived. Audit event coverage and value coverage separately, per pipeline.
  3. The dual-stack architecture means every failure can happen twice, independently. Reconcile across sources weekly.
  4. Defaults matter: conversion values, Primary/Secondary status, and attribution windows quietly steer every automated decision.
  5. Treat tracking as a production system with freshness alarms. Two dead months on one event is a real cost with no invoice.

Next in the series: the iOS-specific failure that even a healthy stack has right now, where iOS drives more purchases than Android and Google Ads attributes almost none of them.

If you want these five checks run against your stack once, properly, that is the core of my app tracking audit.

Sources: first-party implementation and debugging on a dual Firebase + Adjust stack for a Swiss booking marketplace (2026); GA4 documentation on revenue metrics; part one of this series, Firebase or an MMP?.