Rheo documentation
Developer Guide

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

  1. Install the host packages in your app:

    # Expo
    pnpm add expo-superwall
    # Bare React Native
    pnpm add @superwall/react-native-superwall
  2. 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-only SuperwallProvider entry 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 in metro.config.js (Expo Superwall’s exports are import-only). The @rheo/example-expo Metro config shows the mapping.

    Bare React Native:

    import Superwall from '@superwall/react-native-superwall';
    
    Superwall.configure({ apiKey: 'pk_...' });
  3. Define a placement in the Superwall dashboard. Copy the placement name (for example campaign_trigger).

  4. Enable the integration in Rheo: App Settings → Integrations → Superwall. The toggle is required before the canvas enables Superwall on an Integration Node.

  5. 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).

  6. Publish the flow and run it through the SDK — the host triggers <Flow> or useFlow exactly 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:

OutcomeWhen it fires
purchase_completedThe user completed a purchase from the paywall.
restore_completedA restore unlocked access.
purchase_cancelledThe user declined / cancelled the payment sheet without completing a purchase.
dismissedThe user closed the paywall, or Superwall skipped / holdout / already entitled (feature path).
failedSuperwall 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.

KeySet whenNotes
onb_sw_last_eventAlwayspurchase_completed / restore_completed / purchase_cancelled / dismissed / failed.
onb_sw_last_placement_idThe node configured a placement idCopied from the manifest node config.
onb_sw_last_product_idA successful purchase exposed a product idRead 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_free

Expo 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 returns null); 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 failed and 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 / shown
  • surface_outcome — on every resolve, including failed
  • iap_purchase — only on purchase_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

SymptomLikely causeFix
Step always takes Fallback as failedSuperwall package not linked / Expo GoUse 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 blockedSuperwall toggle offEnable App Settings → Integrations → Superwall
No paywall UIPlacement missing or campaign rules skipConfirm placement id matches Superwall dashboard; check campaign rules / holdouts
Purchase not branchingOutcome not wiredConnect purchase_completed or rely on Fallback