Superwall paywall
Present a host-owned Superwall placement as a step in your onboarding flow and branch on the outcome.
Purpose
Add a Superwall paywall as a node inside a Rheo flow. The Rheo SDK registers the placement you configure in Superwall, listens for the outcome, and branches the flow accordingly. Your app stays the source of truth for the Superwall integration — Rheo never configures Superwall API keys for you.
How to
-
Install the host packages in your app:
# Expo pnpm add expo-superwall # Bare React Native pnpm add @superwall/react-native-superwall -
Configure Superwall at app startup with your own API key. Rheo never configures Superwall for you.
Expo: use the compat imperative API so Rheo's default presenter can call
Superwall.shared.register(the hooks-onlySuperwallProviderentry is not enough for that path):import Superwall from 'expo-superwall/compat'; await Superwall.configure({ apiKey: 'pk_...' }); await Superwall.shared.identify({ userId: 'your-app-user-id' });If Metro reports
Cannot find module 'expo-superwall/compat', map the package inmetro.config.js(Expo Superwall’sexportsareimport-only). The@rheo/example-expoMetro config shows the mapping.Bare React Native:
import Superwall from '@superwall/react-native-superwall'; Superwall.configure({ apiKey: 'pk_...' }); -
Define a placement in the Superwall dashboard. Copy the placement name (for example
campaign_trigger). -
Enable the integration in Rheo: App Settings → Integrations → Superwall. The toggle is required before the canvas enables Superwall on an Integration Node.
-
Add an Integration Node from the canvas add menu, then choose Superwall as the provider and set the Placement ID. Connect each outcome handle (or rely on Fallback).
-
Publish the flow and run it through the SDK — the host triggers
<Flow>oruseFlowexactly as before; the placement registers automatically when the flow reaches the node.
Normalized outcomes
The flow engine speaks one of these outcomes per paywall step. Superwall events are mapped by the SDK so authors never see provider-specific event names:
| Outcome | When it fires |
|---|---|
purchase_completed | The user completed a purchase from the paywall. |
restore_completed | A restore unlocked access. |
purchase_cancelled | The user declined / cancelled the payment sheet without completing a purchase. |
dismissed | The user closed the paywall, or Superwall skipped / holdout / already entitled (feature path). |
failed | Superwall reported an error, OR the host did not install a Superwall package, OR placement id is empty. |
Every Integration Node must wire a Fallback edge. It is used for any outcome you do not explicitly map, and the builder blocks publish until it is connected.
Reserved SDK attribute keys
On every outcome, the SDK merges the following keys into the flow's session.sdkAttributes. You can reference these from decision nodes via the existing sdk variable kind without listing them in sdkAttributeKeys — they are auto-allowed.
| Key | Set when | Notes |
|---|---|---|
onb_sw_last_event | Always | purchase_completed / restore_completed / purchase_cancelled / dismissed / failed. |
onb_sw_last_placement_id | The node configured a placement id | Copied from the manifest node config. |
onb_sw_last_product_id | A successful purchase exposed a product id | Read from Superwall dismiss callbacks after purchase_completed. |
Example: branch after the paywall on whether the user purchased.
Decision: "Did the user purchase?"
variable: sdk.onb_sw_last_event
predicate: string eq "purchase_completed"
onTrue: scr_premium_onboarding
onFalse: scr_continue_freeExpo support
The Superwall native modules require a custom dev client / prebuild. They cannot run inside stock Expo Go.
- Run
npx expo prebuild(or maintain a managed dev client) so Superwall is linked. - Expo Go users see the paywall step resolve as
failed(the lazy require returnsnull); your Fallback edge takes over so the rest of the flow stays usable while developing.
Multi-channel behaviour
useFlow is scoped per channel, and so is the surface's pending state. Multiple channels running on the same device handle their own paywall lifecycles independently. Superwall's UI is modal, so two concurrent presentations on the same screen rely on the OS to serialise — Rheo does not install a global lock. In practice, design your channels so only one onboarding surface is on-screen at a time.
Limits and permissions
- Host owns Superwall: Rheo does not install or configure Superwall for you. If the package is missing at runtime, the paywall step resolves as
failedand a warning is logged. - No server-side receipt validation: outcomes are client-reported. If you need server truth, run your own webhook + entitlement reconciliation separately.
- Billing stack: Superwall often sits on StoreKit or RevenueCat underneath. You do not need Rheo's RevenueCat integration enabled to use Superwall; enable RevenueCat in Rheo only when you also use RevenueCat Integration Nodes.
- Late purchases: if a purchase completes after the user already advanced past the paywall, the outcome is ignored. Design your flow assuming the paywall completes before navigation.
Analytics
Three analytics events are emitted automatically:
surface_presented— when the placement is registered / shownsurface_outcome— on every resolve, includingfailediap_purchase— only onpurchase_completed, with product and optional price metadata (provider: "superwall")
See Event catalog for property details.
Advanced: custom presenter
You can override the presenter on a per-useFlow basis for tests or custom UI:
import { useFlow, presentSuperwallPaywall } from '@getrheo/react-native-expo';
const flow = useFlow({
channelId: 'welcome',
externalSurfacePresenter: async (node) => {
if (node.config.provider === 'superwall') {
return presentSuperwallPaywall(node.config);
}
return { outcome: 'failed' };
},
});Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Step always takes Fallback as failed | Superwall package not linked / Expo Go | Use a custom dev client; confirm expo-superwall or @superwall/react-native-superwall is installed |
Cannot find module 'expo-superwall/compat' | Metro cannot resolve the package exports map (import-only, no require) | Map expo-superwall / expo-superwall/compat to build/src/... in metro.config.js (see @rheo/example-expo), or use bare @superwall/react-native-superwall |
| Publish blocked | Superwall toggle off | Enable App Settings → Integrations → Superwall |
| No paywall UI | Placement missing or campaign rules skip | Confirm placement id matches Superwall dashboard; check campaign rules / holdouts |
| Purchase not branching | Outcome not wired | Connect purchase_completed or rely on Fallback |