The symptom comes in three flavors. Your daily actives disagree 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 find
that 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
on the device. RevenueCat does the same; its anonymous IDs look like
$RCAnonymousID:.... Same person, two unrelated identifiers,
and neither system knows about the other. Everything else follows from
that.
The gap then widens 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 lands in one system more than the other, and a handful of internal devices can visibly bend small-app metrics.
- Event delivery. Offline queues, app kills and ad blockers on web views mean the two tools didn't 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 an active user.
What each RevenueCat integration sends
RevenueCat's analytics integrations are server-side: RevenueCat's backend sends events to your tool as purchases, renewals, cancellations and expirations happen, even when the app is closed. The four compared below, from RevenueCat's own documentation as of September 2026:
| Tool | Revenue lands as | Subscription status | Matches users on |
|---|---|---|---|
| PostHog | revenue event property, in USD |
Documented as a user attribute; arrives as an event property (see below) | $posthogUserId, else the app user ID |
| Mixpanel | Event property plus $transactions on the profile |
People property | $mixpanelDistinctId, else the app user ID |
| Amplitude | Amplitude's revenue field, in USD | User property | $amplitudeDeviceId and $amplitudeUserId, else the app user ID |
| Firebase (GA4) | GA4 value and currency via the Measurement Protocol |
No user properties documented | $firebaseAppInstanceId, required |
PostHog, Mixpanel and Amplitude receive rc_-prefixed
lifecycle events (initial purchase, trial started and converted,
renewal, cancellation, expiration and more) with configurable names,
and the subscription status is carried as
rc_subscription_status. Firebase gets fixed names:
purchases and renewals arrive as GA4's standard purchase
event, the rest as rc_trial_start,
rc_expiration and similar. Sandbox events need a separate
key or token in PostHog, Mixpanel and Amplitude.
Sources: RevenueCat's integration pages for PostHog, Mixpanel, Amplitude and Firebase.
PostHog
The integration launched on October 30, 2024
(changelog).
RevenueCat's docs say it updates an rc_subscription_status
user attribute with each event. In an October 2025 community thread, a
user found the value only on events, and a RevenueCat support engineer
confirmed the payload carries no $set object, suggesting a
PostHog pipeline step that copies the property into
$set
(thread).
Until you add that step, you can filter events by status but can't
build a cohort of "people who are subscribed now". PostHog also offers
its own RevenueCat data warehouse
source,
which syncs RevenueCat data into PostHog as tables rather than events.
Mixpanel
Mixpanel gets the most complete profile of the four: the
subscription status as a people property, and revenue both as an event
property and through Mixpanel's own $transactions profile
property. The catch is the ID. Unless you set
$mixpanelDistinctId or share one ID, the events land on a
profile keyed by RevenueCat's app user ID, which Mixpanel's SDK never
saw.
Amplitude
Amplitude accepts both its device ID and user ID and sends both when both are set. RevenueCat's docs add a timing note: expirations arrive within seconds to minutes if Apple's server notifications are configured, and roughly an hour later if they aren't.
Firebase and Google Analytics
Firebase is the strictest. There is no fallback ID:
$firebaseAppInstanceId must be set, and RevenueCat's docs
say an incorrect value keeps events out of Google Analytics. Events go
through the GA4 Measurement Protocol, which Google doesn't allow in
Firebase A/B Testing, so a paywall experiment in Firebase can't use
RevenueCat's purchases as its goal. The integration is available on plans for users who signed up after
September 2023, the legacy Starter and Pro plans, and Enterprise.
What the integrations can't answer
All four are useful for getting purchase events onto an analytics timeline. Four limits apply to all of them:
- The join is only as good as the ID you set. Each integration falls back to RevenueCat's app user ID. If that's RevenueCat's anonymous ID and your analytics SDK never learned it, purchases arrive on users your funnels can't see.
- They're server events. They carry RevenueCat's fields, not the context your analytics SDK adds to its own events, such as the screen or the session. "Which screen came right before the purchase" is a join you still make yourself.
- Earlier history may not be joined. Events flow from the day you connect. Check whether older purchases were sent before you trust cohorts that start before that date.
- Two sources of truth remain. Refunds, renewals, grace periods and expirations update in RevenueCat on their own schedule, and the copy in your analytics tool is only as current as the last event it received.
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 user whose taps you
recorded, joined by construction rather than matched afterwards. It
also makes every integration's fallback line up: the app user ID is
your analytics ID.
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, so 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 paying state against RevenueCat's entitlements on a schedule, as covered in RevenueCat webhooks vs entitlements.
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. If your app does have accounts, decide the identity story before shipping:
- Before login, the user runs under the shared anonymous ID like everyone else.
- At login, RevenueCat can move the anonymous identity to the account ID. Make the same switch in analytics at the same moment, or the timelines fork at the most valuable point of the funnel.
- Restores are where joins die quietly. A restore on a new device can attach purchases to the old identity while your analytics tool mints a new one. Write down which system wins in that collision. RevenueCat's restore behavior setting (transfer to the new ID by default) decides half of it.
Prove the join works: a five-minute check
- 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
- Set the integration's identity attribute (
$posthogUserId,$mixpanelDistinctId,$amplitudeUserIdor$firebaseAppInstanceId) on first launch, before the first purchase can happen. - Exclude internal devices in both tools. Simulator detection plus a manual flag; at indie scale your own usage is a real distortion.
- Compare weekly, not daily. Day-boundary and delivery noise shrinks against the signal.
- Treat RevenueCat as the only truth for money states and never sum revenue from analytics events.
FAQ
Why does RevenueCat show more users than PostHog (or the reverse)?
Different anonymous IDs, different 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 purchase events on a PostHog timeline, yes, if RevenueCat knows your PostHog ID. For segmenting people by subscription status, you need a pipeline step, because the status arrives as an event property.
Which ID does each integration match on?
PostHog: $posthogUserId. Mixpanel:
$mixpanelDistinctId. Amplitude:
$amplitudeDeviceId and $amplitudeUserId. All
three fall back to the RevenueCat app user ID. Firebase requires
$firebaseAppInstanceId and has no fallback.
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.