Data quality · troubleshooting

Why your PostHog and RevenueCat numbers never match (and how to join behavior to revenue)

FolioKit · July 14, 2026 · from debugging this across four production apps

Short answer: your analytics SDK and RevenueCat each invent their own anonymous ID for the same person, so you're running two identity spaces that only overlap by accident. Connectors sync events between them but can't repair the identities. The durable fix is boring: generate one ID and give the same value to both SDKs from the first launch.

The symptom shows up in three flavors. Your DAU disagrees with RevenueCat's active-user count by a suspicious margin. Your funnel dies at paywall_shown because the purchase lives in another tool. Or you try to answer "which onboarding path converts to paid?" and realize no single database can even ask the question.

Two SDKs, two identity spaces

On first launch, your analytics SDK mints an anonymous ID and stores it locally. RevenueCat does exactly the same — its anonymous IDs look like $RCAnonymousID:.... Same human, two unrelated identifiers, and neither system knows about the other. Everything else follows from that.

The divergence then compounds through ordinary app life:

Why the connector doesn't fully fix it

RevenueCat can forward purchase events to analytics tools, and PostHog ships a RevenueCat source. These are genuinely useful — and they're still a sync between two identity spaces. Three limits to know before you rely on one:

  1. The join is only as good as your ID mapping. The connector matches on the app_user_id you gave RevenueCat. If that's RevenueCat's anonymous ID and your analytics tool never learned it, purchase events arrive attached to users your funnels can't see.
  2. History stays broken. Events from before you wired the connector aren't retroactively joined. Your cohorts have a seam in them.
  3. Two sources of truth remain. Refunds, renewals, grace periods, and expirations update in RevenueCat on their own schedule; the copy in your analytics tool drifts unless you re-sync state, not just events.

The one-ID pattern

The fix that doesn't decay: your app generates one identifier and hands the same value to both SDKs before either mints its own. With RevenueCat that means passing it as the app_user_id when you configure the SDK. Every trial, renewal, refund, and expiration then lands on the exact user whose taps you recorded — not "roughly matched", joined by construction.

Practical wrinkles, learned the hard way:

Apps with accounts vs apps without

Most indie apps have no login, and that's the easy case: one generated ID per install, handed to both SDKs, done. If your app does have accounts, decide the identity story before shipping, not after:

Prove the join works: a five-minute validation

  1. Install a fresh build (or reset the simulator) and note the ID your app generated.
  2. Fire a couple of screen events, then make a sandbox purchase.
  3. Look the user up by that one ID: the behavior timeline and the purchase must sit on the same record, in order.
  4. Delete and reinstall, restore purchases, and check again — this is the step that exposes identity forks.
  5. Only then trust a funnel that crosses the money boundary.
Where FolioKit fits: this pattern is the whole product. The SDK generates the user ID and hands the same value to RevenueCat — on existing installs it adopts RevenueCat's ID — and the dashboard reconciles paying status against entitlements daily. Behavior and money, one row. See how the data joins.

If you keep two tools anyway: a triage checklist

  1. Log both IDs on first launch and ship them to both systems as a property — at least the mapping exists somewhere.
  2. Exclude internal devices in both tools (simulator detection plus a manual flag; your own usage is a real distortion at indie scale).
  3. Compare weekly, not daily — day-boundary and delivery noise shrinks relative to the signal.
  4. Treat RevenueCat as the only truth for money states; never sum revenue from analytics events.

FAQ

Why does RevenueCat show more users than PostHog (or the reverse)?

Different anonymous IDs, different install/reinstall handling, different treatment of test devices, and different activity definitions. The counts answer different questions, so neither is "wrong."

Is the PostHog–RevenueCat integration enough?

For revenue events in your analytics timeline, yes. For per-user joins across funnels, retention, and money, only if your ID mapping is airtight — which is precisely the hard part the connector can't do for you.

What ID should I use as the shared one?

Any stable UUID you generate on first launch works. For apps with existing RevenueCat users, adopt RevenueCat's current ID rather than introducing a new one — continuity of purchase history matters more than ID aesthetics.

FolioKit joins what users do with what they pay — one user record across every iOS app you run, an AI brief every Monday, and your AI coding agent does the integration.

Get early access