Why Your RevenueCat Events Never Reach AppsFlyer

RevenueCat only forwards events when the AppsFlyer ID is set before purchase. How we proved a silent 85% event loss, and how to run the same tie-out.

Why Your RevenueCat Events Never Reach AppsFlyer

RevenueCat only forwards a purchase event to AppsFlyer if the $appsflyerId attribute is already on the customer at the moment the purchase happens. RevenueCat's AppsFlyer integration docs say it plainly: attributes must be set "after the Purchases SDK is configured, and before the first purchase occurs," and RevenueCat "will only send events into Appsflyer when the required Customer Attributes are set for the device." If your app sets the ID late, or never, the event is simply never generated. No error, no retry, no alert in either dashboard.

We recently proved this on a live consumer subscription app, and the numbers are worth sharing because the failure mode is invisible until someone ties the systems out user by user. Over one week, the app started 181 trials. Twenty-six trial events arrived in AppsFlyer. Roughly one event in seven was reaching the platform that every downstream ad channel depends on.

What the audit found

We pulled one day of data and accounted for every single user. The app had 25 trial starters that day. We pulled all 25 from the RevenueCat API with the exact timestamp their AppsFlyer ID was written, then pulled the day's raw delivery log from the AppsFlyer integration, row by row. Every user landed in one of three buckets:

AppsFlyer ID timingUsersDelivered to AppsFlyer
ID set before purchase55
ID set after purchase (1 minute to 11 hours late)90
ID never set110

The tie-out was exact. The five users whose ID landed before their purchase were precisely the five events in the delivery log. A late ID never retro-fires the event. A missing ID never fires anything.

The platform split told us where to look. Android delivered 3 of 3. iOS delivered 2 of 22, a 9 percent delivery rate. The iOS attribution handoff was running late or conditionally, so any user who reached the paywall quickly started their trial before the ID landed. The eleven users with no ID at all were also missing $idfv, which meant collectDeviceIdentifiers() was not running for them either (see RevenueCat's customer attributes reference for what those helpers set).

Why no dashboard caught it

Every tool in the stack was internally consistent, which is exactly why nobody noticed. RevenueCat showed every trial. The app's own analytics showed every trial. AppsFlyer showed the trials it received and had no way to know about the ones it did not. Android worked perfectly, so the blended numbers looked plausible rather than broken. There was no error to alert on because, from each system's point of view, nothing was wrong.

This class of bug survives capable engineering teams. It is a timing race between two SDKs, and it is invisible in any single tool. The only way to see it is to join per-user records across the whole chain. If you rely on an MMP for attribution and a subscription platform for purchase events, the seam between them is unmonitored by default.

How to run the tie-out yourself

The method is simple and takes a focused afternoon. You need read access to your subscription platform's API and to your MMP's raw delivery or event logs, and nothing else.

  1. Pick one recent day. Pull every trial starter (or purchaser) from the RevenueCat API, including the timestamp each customer attribute was written.
  2. Pull the raw delivery log for that day from the AppsFlyer integration page, row by row, until the previous day's date appears. That list is every event that actually went out.
  3. Join the two lists per user. Classify each user: ID set before purchase, ID set after purchase, or ID never set.
  4. Check $idfv (iOS) and the Android equivalents on the never-set group. If those are missing too, the device identifier collection call is not running at all for those users.
  5. Compare platforms. A large iOS versus Android delivery gap almost always means the handoff ordering differs between the two codepaths.

If the counts tie out, you have real evidence your pipeline is healthy, which is worth having in writing. If they do not, the gap tells you exactly which call is late.

The fix is call ordering, not architecture

In every version of this failure we have seen, the fix is small: move the setAppsflyerID() and collectDeviceIdentifiers() calls to immediately after Purchases.configure(), before any paywall can transact. The AppsFlyer SDK exposes its UID synchronously, so no callback or await is needed. This is a call-ordering change measured in lines, not a re-architecture, and AppsFlyer's event documentation covers the SDK side if your team wants to verify the flow end to end.

The cost of leaving it broken is not a reporting nuisance. Ad platforms optimize against the events they receive. At one-in-seven delivery, every campaign you run is training on a sliver of your real conversions, your reported CPA is wrong on every channel, and your platform and blended numbers will never reconcile. The damage compounds silently for as long as the gap exists.

If your numbers never quite tie out

We run this exact investigation as a fixed-scope audit: per-user tie-out across your subscription platform, MMP, and ad platforms, with the root cause and the specific fix handed to your engineering team. If your marketing data has never quite matched between tools, read about the tie-out audit here.

attributionAppsFlyerRevenueCatMMPsubscription appsdata integrity

Related Resources