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 iOS SDK 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
- Sign up for Paddle and complete their onboarding checklist (KYC, banking, tax info). Live payments are blocked until this is done.
- 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.
3. Default payment link
- Go to Paddle website approval and add a new domain.
- Go to Paddle Checkout Settings and find the Default payment link section. Paste in the same domain you just added for Paddle website approval.
- 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.
- Intro pricing (e.g., $1.99 for the first 3 months, then $9.99/mo) is not yet supported.
6. Build your paywalls
Paddle checkout requires two paywalls:- The original paywall, shown in-app, containing your Paddle products (and optionally iOS products).
- A hosted web paywall, which runs the actual Paddle checkout in the user’s browser on
clickthrough.to.
- Create the original paywall and add Paddle 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.
7. SDK integration
- iOS (native) 4.4.7+
- Expo 3.4.4+
- Flutter 3.3.4+
Enable external web checkout before initializing Helium:The
successURL and cancelURL are set once, globally. Make sure both are registered as deep links in your app (via scheme or universal link).Set your user ID on Helium before the user reaches a paywall. User IDs are strongly preferred. If one isn’t set at purchase time, the SDK falls back to a persistent device ID and reconciles after login. If you must set user ID after showing paywall, let us know — there is a workaround.Handle the post-purchase return. When checkout completes, the user is deep-linked back to your app via the success 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**:8. (Optional) Connect RevenueCat
If you use RevenueCat for entitlement management:- Create a Paddle web configuration in the RevenueCat dashboard.
- Select your project > Web > Add web provider > Paddle
- Create a new Paddle API Key for RevenueCat. See permissions below
- Paste in the new Paddle API key
- Purchase tracking should be Automatic
- Under App user ID matching select Use a custom field with
rc_user_idas the field key
- Go to Product catalog > Products and Import your Paddle products into RevenueCat.
- Attach those products to desired entitlement(s).
Permissions for Paddle API Key that RevenueCat needs
Permissions for Paddle API Key that RevenueCat needs
- 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
Troubleshooting RevenueCat Entitlements for Paddle Purchases
Troubleshooting RevenueCat Entitlements for Paddle Purchases
- 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
Run through these scenarios in Paddle’s sandbox before going live:- Happy path purchase. Open a Paddle paywall in-app, tap a product, complete checkout in the browser, and confirm you’re deep-linked back with an active entitlement.
- Entitlement sync. After purchase, confirm
hasActivePaddleEntitlement()returnstrueimmediately, and that RevenueCat (if connected) updates within a few seconds. - Free trial. Purchase a trial product, cancel it, and confirm the trial is no longer offered on re-purchase.
- Cancel flow. Open the customer portal from your app, cancel a subscription, and confirm the entitlement expires at period end.
- Pre-login purchase. Make a purchase before setting a user ID. After you set the user ID, confirm the entitlement attaches correctly.
- Abandoned checkout. Close the browser mid-checkout and confirm the app handles the cancel URL gracefully.
- Webhook delivery. In the Paddle dashboard, confirm webhooks are reaching Helium with 2xx responses.
Appendix
How entitlements work
Helium maps youruser_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 fromclickthrough.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.
Why are success/cancel URLs set in the SDK?
Deep links are specific to your app build, so it’s cleaner to configure them alongside SDK init than in the dashboard. They’re global: set once, applied to every Paddle purchase. It’s ok if they are the same URL.Known limitations and gotchas
- Intro pricing (
$X for first N months, then $Y/mo) is not yet supported. - 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: if you support multiple languages in-app, confirm your hosted web paywall’s localizations match.
- Prepaid card failures / declined payments: the user is returned via the
cancelURL. Your app should treat this as “no purchase made.”