What You’ll Accomplish
By the end of this guide, you’ll have:- A Stripe account connected to Helium, with products synced and ready to add to paywalls.
- Paywalls that offer Stripe products.
- A hosted web checkout flow that lets users purchase in their browser and deep-links them back into your app.
- Entitlement tracking across Stripe, the Helium SDK, and (optionally) RevenueCat.
Prerequisites
Before you start, make sure you have:- The Helium SDK already integrated in your app.
- A deep link scheme or universal link configured in your app — users need a way back into your app after purchase.
- Admin access to your Helium dashboard.
Implementation Steps
1. Connect Stripe to Helium
Go to the Helium Integrations page, click the Stripe tab, and Connect Stripe. Create a new account or connect an existing Stripe account. Sync your products and ensure the APPLE PAY DOMAIN status is Verified.Helium does not yet support Stripe sandbox.
2. Adjust Stripe Products
Assign Stripe products to appropriate buckets. Add free/paid trials as needed.3. Build your paywalls
For a short walkthrough, see Build an App2Web paywall. Stripe checkout requires two paywalls:- The original paywall, shown in-app, containing your Stripe products (we recommend adding iOS products too as a backup if user decides to not use Stripe).
- A hosted web paywall, which runs the actual Stripe checkout in the user’s browser on
clickthrough.to.
- Create the original paywall and add Stripe products to it.
- Create a hosted web paywall (the editor will prompt you).
- Link the two. The original paywall can’t be published without a linked hosted web paywall.
4. SDK integration
- iOS (native, Helium 4.6.0+)
- Expo (Helium 3.4.13+)
- Flutter (Helium 3.3.8+)
Enable external web checkout before initializing Helium:The Set your user ID on Helium before the user reaches a paywall. If you must set user ID after showing a paywall, set the
redirectURL is set once, globally. Checkout redirects back to it whether the purchase succeeds, is canceled, or fails. Make sure it’s registered as a deep link in your app (via scheme or universal link).On older SDK versions that don’t have
redirectURL, use the equivalent two-URL form with the same URL for both: enableExternalWebCheckout(successURL:cancelURL:paymentProcessors:). That form still works, but is deprecated.allowWebCheckoutWithoutUserId option to true.Handle the checkout return. Whether checkout succeeds, is canceled, or fails, the user is deep-linked back to your app via the redirect URL. For a smoother handoff, call Helium.shared.handleURL(url) from your onOpenURL / application(_:open:) handler.Add a “Manage Subscription” button so users can cancel or update payment methods via Stripe’s customer portal:5. (Optional) Connect RevenueCat
If you use RevenueCat alongside Stripe, follow these additional steps so that RevenueCat will be able to track entitlements for Stripe purchases:1
Visit your RevenueCat Account Settings and find the Connect Stripe button
2
Install the Revenue Cat app to your Stripe account
3
Open RevenueCat Dashboard
- Open the dashboard: https://app.revenuecat.com/
- Select your Project > Apps & Providers > Add Web Config
- Find your Stripe account and complete the flow
4
Select the Stripe web configuration you just added and configure Stripe webhooks to send to RevenueCat
- Open the dashboard: https://www.revenuecat.com/docs/platform-resources/server-notifications/stripe-server-notifications
- Make sure to include these events:
- customer.subscription.updated
- customer.subscription.deleted
- charge.refunded
- invoice.updated
- customer.subscription.created
- checkout.session.completed
5
Enable Track new purchases from server-to-server notifications
Make sure to enter rc_user_id as the Metadata field key:.png?fit=max&auto=format&n=xncQKV6C2-wx-EJ0&q=85&s=2dbf0053a50f3cf8c4b9d7df707d632d)
.png?fit=max&auto=format&n=xncQKV6C2-wx-EJ0&q=85&s=2dbf0053a50f3cf8c4b9d7df707d632d)
6
Configure your Stripe products with RevenueCat
- Visit Product catalog for your desired project in RevenueCat dashboard.
- In the Products tab, find the Stripe section and add Stripe products
- Go to Entitlements tab, select desired entitlement/s, click Attach and add the Stripe products you just created
- If you rely on Offerings in your RevenueCat integration, you can also add the Stripe products there
Testing
To test without affecting production traffic, create a test targeting group that includes:- Stripe eligible users
sandboxordebugenvironment
- Happy path purchase. Open an App2Web paywall in-app, tap a web product, and confirm the app correctly opens the browser. Complete the standard purchase flow, then confirm you’re deep-linked back with an active entitlement. Dismiss the paywall before purchasing as well, and confirm the app returns to the expected screen.
- Entitlement sync. After purchase, confirm
hasActiveStripeEntitlement()returnstrueimmediately, and that RevenueCat (if connected) updates within a few seconds. - Cancel flow. Open the customer portal from your app, cancel a subscription, and confirm the entitlement expires at period end.
- Canceled checkout. Back out of the checkout page without paying and confirm you’re deep-linked back to the app with no entitlement granted and no success state shown.
- Pre-login purchase (if applicable). If your App2Web paywall can be shown before you set a durable
user_id, make a purchase before setting it. Set the durable user ID afterwards and confirm the entitlement attaches correctly.
Appendix
How entitlements work
Testing sign-in, access, or RevenueCat delivery? See How to test App2Web entitlements. Helium maps youruser_id to Stripe’s customer_id. If the user isn’t logged in at purchase time, we use the helium_persistent_id as a stand-in and reconcile once a real user ID is set.
On successful purchase, the SDK’s entitlement cache updates immediately. The Helium backend processes Stripe’s webhook to update the customer mapping and forward events to your analytics/revenue pipeline. Entitlements are also checked server-side (which ultimately reflects Stripe’s transaction state), so they stay consistent across devices.
Why two paywalls?
The original in-app paywall presents your Stripe offerings. The hosted web paywall — served fromclickthrough.to — runs the actual Stripe checkout, which has to happen in a browser. The two can be customized independently and don’t need to show the same products.
Apple Pay readiness
Web checkout on iOS pays with Apple Pay. A small share of users open it from a browser that cannot complete an Apple Pay payment, and they come back to the app without buying. Apple Pay readiness keeps those users out of web checkout so you can route them to an in-app purchase paywall instead. How it works. On launch, the SDK checks whether Apple Pay can pay on the checkout domain and reports the result to Helium as thewebApplePayReadiness targeting variable. The Stripe A2W Eligible targeting preset excludes users whose result is notReady. Those users no longer match your App2Web audience, so give them an IAP-only rule later in the same workflow, or they get no paywall. Everyone else keeps seeing web checkout, including users on older SDKs and users whose result is unknown.
The result is an optimization signal, not a guarantee, since the user’s Wallet can change between the check and checkout. Presenting a paywall does not wait for it, apart from the first-launch wait described under Launch timing.
Versions. iOS only. Measurement is on by default from Helium iOS 4.13.0, expo-helium 3.6.3, @tryheliumai/paywall-sdk-react-native 3.1.13, and helium_flutter 3.3.14. Earlier versions report ready without measuring, unless you opted in during the beta.
Measurement runs only when external web checkout is already configured when the launch request is sent. Call enableExternalWebCheckout before you initialize Helium, as shown above. If you configure it later, users report unknown:notMeasured and the readiness rule excludes no one.
Turning it off. Set the flag before you initialize Helium. Users then report unknown:notMeasured and keep seeing web checkout.
- iOS (native)
- Expo
- React Native
- Flutter
true. A value fetched after initialization only takes effect on the next launch.
Launch timing. On the first launch with measurement on, after install or after updating to a version that measures by default, the SDK waits up to two seconds for the measurement before it requests your paywall configuration. Later launches send the stored result immediately and re-measure in the background. A launch into the background skips the measurement and the wait, and the SDK measures once the app becomes active.
Apps that declare WKAppBoundDomains. Add bundles.clickthrough.to to that list in your Info.plist. Without it, iOS blocks the check: users report unknown:probeFailed on that first launch and unknown:notMeasured afterwards. Checkout still works and users still see web checkout, but users who are not ready are no longer excluded. Apps that don’t declare WKAppBoundDomains need no change.
Targeting. webApplePayReadiness is ready, notReady, or unknown:<reason> when the measurement could not answer. The Stripe A2W Eligible preset already handles it. If you built a Stripe audience without the preset, add a rule that webApplePayReadiness is not notReady. If an audience uses the standalone Browser Apple Pay ready preset from the beta, keep it unless you also add the Stripe A2W Eligible preset to that audience, since it may be the only readiness rule there.
Known limitations and gotchas
- Availability: Stripe App2Web is currently supported for US storefronts only.
- Publish states: both the original paywall and its linked hosted web paywall must be published before the flow works end-to-end.
- Localizations: hosted web paywalls currently support one language.
- Testing: App2Web previews and SDK requirements cover simulated purchases and real purchases on eligible US devices. Also test through a real trigger to verify targeting and your post-purchase handlers.