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:
- Reinstalls — the analytics tool mints a fresh ID; RevenueCat may restore the old identity through the purchase history. One person, three identifiers.
- Sandbox, TestFlight, and simulators — your own testing pollutes one system more than the other; a handful of internal devices can visibly bend small-app metrics.
- Event delivery — offline queues, ad blockers on webviews, and app kills mean the two tools didn't even see the same sessions.
- Timezones and definitions — "active on Tuesday" isn't the same predicate in two products with different day boundaries and different ideas of what an active user is.
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:
- 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.
- History stays broken. Events from before you wired the connector aren't retroactively joined. Your cohorts have a seam in them.
- 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:
- Existing apps with live subscribers: don't rotate IDs out from under RevenueCat — adopt its existing ID as your analytics ID instead. Purchase history and entitlements stay continuous, and analytics joins from that day forward.
- "Currently paying" needs reconciliation, not events. If you flag payers from purchase events alone, expirations quietly rot the flag. Reconcile the paying state against RevenueCat's entitlements on a schedule so "paying users" means paying now.
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:
- Before login, the user runs under the shared anonymous ID like everyone else.
- At login, RevenueCat can alias the anonymous identity to the account ID. Do the same switch in analytics at the same moment, or the timelines fork right at the most valuable point of the funnel.
- Aliases and restores are where joins die quietly. A restore on a new device attaches purchases to the old identity while your analytics tool happily mints a new one. Whatever tool you use, write down which system wins in that collision — if you can't answer it, your revenue joins are aspirational.
Prove the join works: a five-minute validation
- Install a fresh build (or reset the simulator) and note the ID your app generated.
- Fire a couple of screen events, then make a sandbox purchase.
- Look the user up by that one ID: the behavior timeline and the purchase must sit on the same record, in order.
- Delete and reinstall, restore purchases, and check again — this is the step that exposes identity forks.
- Only then trust a funnel that crosses the money boundary.
If you keep two tools anyway: a triage checklist
- Log both IDs on first launch and ship them to both systems as a property — at least the mapping exists somewhere.
- Exclude internal devices in both tools (simulator detection plus a manual flag; your own usage is a real distortion at indie scale).
- Compare weekly, not daily — day-boundary and delivery noise shrinks relative to the signal.
- 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.