Skip to main content
This guide walks you through integrating Paddle with Helium so you can sell web-based subscriptions alongside (or instead of) your iOS in-app purchases.

What You’ll Accomplish

By the end of this guide, you’ll have:
  • A Paddle account connected to Helium, with products synced and ready to add to paywalls.
  • Paywalls that offer Paddle products — either on their own or alongside iOS IAPs.
  • A hosted web checkout flow that lets users purchase in their browser and deep-links them back into your app.
  • Entitlement tracking across Paddle, the Helium SDK, and (optionally) RevenueCat.
  • Free trials with built-in abuse protection (one trial per customer, per lifetime).
  • A “Manage Subscription” flow that routes users to Paddle’s customer portal.

Prerequisites

Before you start, make sure you have:
  • A Paddle account. If you don’t have one, start with Paddle’s setup checklist and onboarding overview.
  • The Helium SDK (iOS native, React Native/Expo, or Flutter) already integrated in your app.
  • A deep link scheme configured in your app — users need a way back into your app after purchase.
  • Admin access to your Helium dashboard.
  • (Optional) A RevenueCat account, if you want Paddle purchases to reflect in RevenueCat entitlements.

Implementation Steps

1. Set up your Paddle account

  1. Sign up for Paddle and complete their onboarding checklist (KYC, banking, tax info). Live payments are blocked until this is done.
  2. Add your products and prices in the Paddle dashboard. Paddle’s product creation guide is the canonical reference.

2. Connect Paddle to Helium

Create an API key in Paddle with the following permissions: Add the API key to Helium. In the Helium dashboard, go to Settings → Integrations → Paddle and paste your key. Configure the webhook. Helium will show you a webhook URL — create a matching webhook in Paddle, then copy the webhook’s signing secret back into Helium. This powers revenue reporting and entitlement updates.
  1. Go to Paddle website approval and add a new domain.
  2. Go to Paddle Checkout Settings and find the Default payment link section. Paste in the same domain you just added for Paddle website approval.
  3. At the top of the same page, make sure Apple Pay has been selected as a Payment Method

4. Sync your Paddle products

In the Helium dashboard, click Sync Products. Your Paddle products and prices will appear alongside your iOS products and can be added to paywalls.

5. (Optional) Add free trials

To offer a free trial on a Paddle product, configure it on the product in Helium. A few things to know:
  • One trial per customer, ever. Paddle doesn’t natively prevent trial abuse (a user could start a trial, cancel, and start another), so Helium enforces this at the entitlement layer. Once a customer has held a trial — even if they cancel — they won’t be offered another.
  • A payment method is required up front. Payment is deferred, not skipped.

6. Build your paywalls

For a short walkthrough, see Build an App2Web paywall. Paddle checkout requires two paywalls:
  1. The original paywall, shown in-app, containing your Paddle products (and optionally iOS products).
  2. A hosted web paywall, which runs the actual Paddle checkout in the user’s browser on clickthrough.to.
When the user taps a CTA on the original paywall, they’re kicked out to the hosted web paywall, where they complete the purchase. To set this up:
  1. Create the original paywall and add Paddle products to it.
  2. Create a hosted web paywall (the editor will prompt you).
  3. Link the two. The original paywall can’t be published without a linked hosted web paywall.
The hosted web paywall doesn’t have to show the same products as the original — you can use it to customize the checkout experience (e.g., upsell a different plan at purchase time). Hosted web paywalls can’t be added to workflows, experiments, or targeting rules; they exist only to back a Paddle paywall.

7. SDK integration

Enable external web checkout before initializing Helium:
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.
Set your user ID on Helium before the user reaches a paywall. If you must set user ID after showing a paywall, set the 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 Paddle’s customer portal:
The portal link opens paddle.net, where the user enters the email they purchased with, receives a magic link, and lands in the customer portal. This is Paddle’s default, recommended flow: a secure email verification step in front of the portal.Want a pre-authenticated portal link? If you’d rather skip the paddle.net email verification step and drop users straight into the customer portal, generate the link from your backend instead. Look up the user’s Paddle customer ID with Helium:
Send that ID to your server and follow Paddle’s guide for generating pre-authenticated customer portal links.

8. (Optional) Connect RevenueCat

If you use RevenueCat for entitlement management:
  1. Create a Paddle web configuration in the RevenueCat dashboard.
    1. Select your project > Web > Add web provider > Paddle
    2. Create a new Paddle API Key for RevenueCat. See permissions below
    3. Paste in the new Paddle API key
    4. Purchase tracking should be Automatic
    5. Under App user ID matching select Use a custom field with rc_user_id as the field key
  2. Go to Product catalog > Products and Import your Paddle products into RevenueCat.
  3. Attach those products to desired entitlement(s).
  • Addresses  READ
  • Adjustments - READ
  • Businesses - READ
  • Client-side tokens - WRITE
  • Customer portal sessions - WRITE
  • Customers - READ
  • Discounts - READ
  • Notification settings - WRITE
  • Notifications - READ
  • Payment methods - READ
  • Prices - READ
  • Products - READ
  • Subscriptions - READ
  • Transactions - WRITE
RevenueCat entitlements take a couple seconds to reflect Paddle purchases. For instant post-purchase UX, check Helium entitlements in addition to RevenueCat — the SDK will push RevenueCat to sync as quickly as possible, but Helium’s own entitlement is always the fastest source of truth.
  • Make sure you have done the above steps and include all of your Paddle products in your RevenueCat entitlements.
  • Find your Paddle -> RevenueCat webhook at https://vendors.paddle.com/notifications-v2 and click on it to see that notifications are being sent successfully. If you see errors, you may need to adjust the permissions on the Paddle API Key you provided to RevenueCat.
  • If you still have issues, delete the existing Paddle web provider in RevenueCat, revoke the Paddle API Key you created for RevenueCat (leave the Helium one), and run through the above steps again.

Testing

To test without affecting production traffic, create a test targeting group that includes:
  • Paddle eligible users
  • sandbox or debug environment
Target the App2Web paywall to that group, then run through these scenarios in Paddle’s sandbox before going live:
  1. 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.
  2. Entitlement sync. After purchase, confirm hasActivePaddleEntitlement() returns true immediately, and that RevenueCat (if connected) updates within a few seconds.
  3. Cancel flow. Open the customer portal from your app, cancel a subscription, and confirm the entitlement expires at period end.
  4. 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.
  5. Webhook delivery. In the Paddle dashboard, confirm webhooks are reaching Helium with 2xx responses.
  6. 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.
App2Web paywall not appearing? Eligibility is checked server-side against the device’s locale country, App Store storefront country, and IP location — all three must be US, and there is no client-side error when a device is filtered out. If you’re testing from the US, disable VPNs. If you’re testing from outside the US: set your device region to the United States, sign in with a sandbox Apple account on the US storefront, and connect through a US VPN so your IP resolves to the US.

Appendix

How entitlements work

Testing sign-in, access, or RevenueCat delivery? See How to test App2Web entitlements. Helium maps your user_id to Paddle’s customer_id. Paddle deduplicates customers strictly by email, so two Paddle customers can’t share an address. 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 Paddle’s webhook to update the customer mapping and forward events to your analytics/revenue pipeline. Entitlements are also checked server-side (which ultimately reflects Paddle’s transaction state), so they stay consistent across devices.

Why two paywalls?

The original in-app paywall presents your Paddle offerings. The hosted web paywall — served from clickthrough.to — runs the actual Paddle 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 the webApplePayReadiness targeting variable. The Paddle 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.
If you drive the flag from remote config, read the value cached from the previous session and default to 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 Paddle A2W Eligible preset already handles it. If you built a Paddle 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 Paddle A2W Eligible preset to that audience, since it may be the only readiness rule there.

Known limitations and gotchas

  • Availability: Paddle App2Web is currently iOS-only and supported for US storefronts only. iOS-only applies across all SDKs — in React Native and Flutter apps, the web-checkout APIs are safe no-ops on Android, so shared code can ship unchanged and Android users keep your regular in-app purchase flow.
  • California compliance for subscription cancellation: guidance coming soon.
  • 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: Paywall Previews and SDK requirements cover simulated purchases and real purchases on eligible devices. Before launch, also open the flow through a real trigger: previews bypass targeting and do not exercise paywall traits or post-purchase handlers.

Before you go live

Run through the App2Web go-live checklist before sending traffic to this flow. See the App2Web FAQ for attribution, checkout behavior, and evaluating experiment results.