
How to use Rheo with RevenueCat
Orchestrate onboarding around RevenueCat paywalls with native UI, OTA updates, and branch on purchase outcomes without replacing your billing stack.
RevenueCat is the source of truth for subscriptions in thousands of mobile apps. Rheo is the source of truth for in-app onboarding and experimentation. They solve different problems and work best together.
This guide walks through integration end to end: what each product owns, how to add a RevenueCat paywall step to a Rheo flow, how outcomes branch the journey, and how analytics line up so you can optimize trial starts without shipping a new App Store build for every test.
Division of responsibility
| Layer | RevenueCat | Rheo |
|---|---|---|
| SDK configure and API keys | Yes | No |
| Products, offerings, entitlements | Yes | No |
| Paywall UI presentation | Yes (via react-native-purchases-ui) | Triggers presentation at flow step |
| Onboarding screens and branches | No | Yes |
| OTA copy and layout changes | Paywall templates (RC dashboard) | Full native flow graph |
| A/B tests on full journey | Limited to RC experiments | Channel experiments on flow variants |
| Step-level funnel analytics | Purchase events | Per-screen + paywall outcomes |
Rule of thumb: RevenueCat answers "what can they buy and did billing succeed?" Rheo answers "what did they see before and after, and which variant won?"
Rheo never calls Purchases.configure. Your app remains the host integration. That keeps keys, entitlements, and compliance where your engineers already manage them.
Prerequisites
Before you add a paywall node in the builder:
-
React Native app with Rheo SDK integrated (
@getrheo/react-native-expoor bare equivalent). -
RevenueCat packages in the host app:
pnpm add react-native-purchases react-native-purchases-ui -
Configure at startup (your code, not Rheo's):
import Purchases from 'react-native-purchases'; Purchases.configure({ apiKey: process.env.EXPO_PUBLIC_RC_KEY! }); -
Expo: custom dev client or prebuild. RevenueCat native modules do not run in stock Expo Go. Use a Fallback branch so flows stay testable in Go.
-
Offering created in the RevenueCat dashboard with identifiers you will reference from Rheo.
Enable RevenueCat in Rheo
- Open App settings → Integrations → RevenueCat.
- Toggle the integration on and save a default offering id.
- Publish flows only after the toggle is on. The builder blocks paywall nodes when the integration is disabled.
The toggle is required before the canvas exposes the RevenueCat paywall external surface in the add menu.
Add a paywall step to a flow
- Open your flow in the flow editor.
- From the add menu, insert RevenueCat paywall.
- Configure offering (and optional placement) to match RevenueCat.
- Connect outcome edges:
purchase_completedrestore_completeddismissedfailed
- Connect Fallback (required). Rheo blocks publish if Fallback is missing.
Fallback covers any outcome you do not explicitly wire, and keeps the flow usable when the paywall UI module is missing in dev.
Normalized outcomes
The flow engine uses four outcomes regardless of RevenueCat's internal event names:
| Outcome | When it fires |
|---|---|
purchase_completed | User completed a purchase from the paywall |
restore_completed | Restore unlocked a new entitlement |
dismissed | User closed the paywall, or presentation was skipped |
failed | RevenueCat error, or react-native-purchases-ui not installed |
Map each outcome to a screen, decision, or end state. Example: purchase_completed → premium welcome; dismissed → limited free tier with upsell reminder.
Branch with SDK attributes
On every paywall resolve, the SDK merges reserved keys into session.sdkAttributes:
| Key | Set when |
|---|---|
onb_rc_last_event | Always (purchase_completed, restore_completed, dismissed, failed) |
onb_rc_last_offering_id | Node configured an offering id |
onb_rc_last_product_id | Successful purchase exposed a product id |
onb_rc_last_period_type | normal, intro, or trial when observed |
Use a decision node after the paywall without listing these keys in sdkAttributeKeys:
Decision: "Did the user purchase?"
variable: sdk.onb_rc_last_event
predicate: string eq "purchase_completed"
onTrue: scr_premium_onboarding
onFalse: scr_continue_free
That pattern supports second-chance offers, feature gates, and support paths based on real billing outcomes.
Publish and assign to a channel
- Click Publish in the flow editor. Add an optional version label.
- Pin the version to a channel, or attach the channel to an experiment with multiple flow variants.
- In the app, pass the channel id to
<Flow channelId="ch_live_…" />oruseFlow.
When the user reaches the paywall node, the SDK presents RevenueCat's UI. No extra app code per test.
Analytics you get automatically
Rheo emits:
surface_presentedwhen the paywall is shownsurface_outcomeon every resolve, includingfailediap_purchaseonpurchase_completedwith product metadata
These events carry experiment and variant ids when the channel runs an A/B test. Combine with per-screen funnel metrics on the flow Analytics tab to see where users stall before they ever see the paywall.
RevenueCat's dashboard remains authoritative for revenue, churn, and cohort LTV. Rheo is authoritative for onboarding completion and step drop-off.
Experiment: onboarding path + paywall timing
A common first test:
- Control: Four onboarding screens, then paywall.
- Variant: Two screens, then paywall, then optional education after purchase.
Duplicate the baseline flow, edit the variant, publish both versions, and start a channel experiment with a scheduled end date. Read pairwise lift vs control on completion and purchase outcomes at pending decision.
Do not change traffic weights mid-flight without documentation. Extend if sample size is thin.
Multi-channel and edge cases
Multiple channels on one device each maintain their own flow state. RevenueCat's modal UI is system-modal; design so only one onboarding surface is on screen at a time.
Late purchases: if a purchase completes after the user already advanced past the paywall step, the outcome is ignored. Assume the paywall step completes before navigation.
Restore without new entitlement: lands on dismissed, not restore_completed. Wire both if you need different UX.
Server-side truth: outcomes are client-reported. Use RevenueCat webhooks and your backend for entitlement enforcement if required.
Troubleshooting
| Symptom | What to check |
|---|---|
| Paywall never appears | react-native-purchases-ui installed; Purchases.configure ran |
Step resolves failed in Expo Go | Expected; Fallback should advance the flow |
| Publish blocked | Integration toggle; Fallback edge connected |
| Restore branches wrong | Only new entitlements fire restore_completed |
Custom presenter (advanced)
For tests or custom UI, override presentation per useFlow:
useFlow({
channelId: 'ch_test_xyz',
externalSurfacePresenter: async () => ({ outcome: 'purchase_completed' }),
});
Use sparingly in production; RevenueCat UI is the default path.
Test vs live channels
Keep billing keys and channel ids separate:
| Channel | RevenueCat keys | Rheo environment |
|---|---|---|
| Internal QA | Sandbox / test store | test |
| Production | Live keys | live |
Point staging builds at ch_test_… until flows validate. Never pin an experimental graph to live without QA on device with real store sandbox purchases.
Translations and localized paywalls
Rheo supports translations on flow screens from the editor. RevenueCat paywall templates may have their own localization. For a coherent experience:
- Localize Rheo screens for each locale you support.
- Configure matching locale offerings in RevenueCat if products differ by region.
- Filter flow analytics by locale in the completion heatmap to catch truncation or awkward translations before they depress trials.
Approvals and governance
Workspaces on Grow and Scale may use publish approvals on sensitive channels. RevenueCat price changes still go through App Store Connect / Play Console; Rheo changes do not replace store admin process. Document who can pin live channels vs who can draft flows.
Instrumenting custom attributes
Pass business context from the host app via sdkAttributes on <RheoProvider>:
<RheoProvider
userId={stableUserId}
sdkAttributes={{
install_source: 'paid_social',
campaign_id: 'summer_2026',
}}
>
Use decision nodes to branch on those attributes (e.g., shorter onboarding for organic users). Combine with onb_rc_last_event after paywall for rich segments in analytics custom property cohort charts.
Expo and bare workflow notes
Expo dev client: After npx expo prebuild, verify paywall presentation on a physical device with sandbox Apple ID. Simulators work for layout but billing edge cases appear on device.
Bare React Native: Ensure react-native-purchases-ui is linked and ProGuard rules (Android) do not strip RevenueCat classes.
Prefetch: Rheo SDK prefetch is silent and does not emit experiment exposure. Use prefetch for faster first paint; do not use it to "pre-show" paywalls.
Versioning and rollback
Published versions are immutable. To rollback:
- Open the flow version history.
- Pin the channel to the previous known-good version.
- No App Store release required.
Label versions clearly (2026-07-10-rc-offering-summer) so rollback is one click under incident pressure.
Summary workflow
- Configure RevenueCat in the host app.
- Enable integration in Rheo app settings.
- Build onboarding → paywall → branches on the canvas.
- Publish, pin or experiment on a channel.
- Read funnel +
surface_outcomeanalytics; iterate OTA.
You keep RevenueCat for billing. Rheo gives you a native, measurable journey around it. The integration is intentionally thin: Rheo orchestrates, RevenueCat settles transactions, your app stays in control of keys and entitlements.