SubSovereign on React Native — integration guide
This guide covers @subsovereign/react-native (sdk-react-native/) — a data client and a
native paywall renderer in one package. The paywall you design in the SubSovereign dashboard renders
here with native RN primitives (View/Text/Pressable), from the same config as the iOS,
Android, web, React and Flutter SDKs.
Already using
@subsovereign/js-sdkin React Native? That works too (see the Web & React Native guide) — this package adds the native<Paywall>component and a data client with no native dependencies (plainfetch).
Before you start
A running SubSovereign server, an app registered in the dashboard (an appId and an
SDK-role API key), access levels (e.g. pro) linked to your store products, and a
published paywall.
Step 1 — Install
The package lives in sdk-react-native/ and is not on npm yet — add it as a path or git
dependency (or copy the folder into your project):
npm install ./sdk-react-native # path dependency while it's pre-npm
Optional niceties, used automatically when present (never hard dependencies):
npx expo install expo-linear-gradient # gradient paywall backgrounds
npm install react-native-video # hero video (falls back to image without it)
Step 2 — Configure once, when your app starts
import { SubSovereign } from '@subsovereign/react-native';
SubSovereign.configure({
apiKey: 'YOUR_SDK_KEY',
appId: 'your-app-id',
baseUrl: 'https://subs.yourdomain.com/api/v1', // YOUR self-hosted server
userId: currentUser.id, // your own stable user id
locale: 'en',
});
Step 3 — Check what the user can access
const result = await SubSovereign.checkEntitlements();
if (result.hasAccess) showPremiumContent();
else showPaywall();
Wrap it in try/catch; on error keep the user on their last-known access and retry later — never
lock a paying user out over a network blip.
Step 4 — Show the paywall and sell
import { Paywall, usePaywallConfig } from '@subsovereign/react-native';
import { Platform } from 'react-native';
const config = await SubSovereign.getPaywallConfig(Platform.OS); // or usePaywallConfig(params)
<Paywall config={config} onSelectProduct={(productId) => buy(productId)} />
Run the purchase with your billing library as usual, then have the server verify it:
// Android (Play Billing) — built into this client:
await SubSovereign.validateGooglePurchase({ purchaseToken, productId, accessLevelId: 'pro' });
// iOS (StoreKit): use the Web/JS data SDK's validateApplePurchase —
// this client does not ship an Apple call. See web.en.md Step 4.
Re-run checkEntitlements() afterwards and unlock.
Privacy (GDPR)
await SubSovereign.recordConsent({ purpose: 'analytics', granted: true });
EU withdrawal button (Compliance Passport)
What happens to the money. When a customer completes this screen, we record the withdrawal and stop the subscription renewing at the end of the period they have already paid for — they keep access until then. For a Stripe subscription we tell Stripe directly, using the keys you saved for this app; you can turn that off under When a customer cancels under EU law on the app's page in the dashboard, and if you do, stopping the renewal becomes your job. Apple, Google, Amazon and Roku do not allow a merchant to cancel on a customer's behalf — for those the customer must also cancel in the store, and you should say so in your own cancellation copy.
Confirming receipt is yours to do, and it is a legal duty. EU law requires the seller to send the
customer an acknowledgement of receipt, on a durable medium such as email, without delay, showing the
date and time. We do not send it and cannot: we hold no email address for your customers, by design.
The customer is signed in to your app, and the successful cancel call returns the withdrawal reference
and the exact submission time — send them the letter below from your own system at that moment, and
keep a record of when you sent it. In React Native the drop-in <WithdrawalButton /> cannot hand the receipt back yet: call SubSovereign.withdraw(subscriptionId, { channel: Platform.OS === 'ios' ? 'ios' : 'android' }) from your own control and use the receipt it returns — pass channel exactly as the button does, because without it the call files the withdrawal as android on an iPhone.
Subject: Your withdrawal has been received
Hello,
We confirm we received your withdrawal from your subscription on
{submittedAt}. Your reference is{withdrawalId}.Your subscription will not auto-renew. This is your acknowledgement of receipt.
submittedAt and withdrawalId are the two fields the successful cancel call returns.
import { WithdrawalButton } from '@subsovereign/react-native';
<WithdrawalButton subscriptionId={sub.id} />
Shows the prominent labelled button, confirms once, and submits idempotently. Note: this
button does not fetch the app's withdrawal config first — if you disable or jurisdiction-limit
withdrawals in the dashboard, gate its visibility yourself with
SubSovereign.getWithdrawalConfig(). SubSovereign.withdraw(subscriptionId, opts) is also
available for custom UIs (pass channel: Platform.OS === 'ios' ? 'ios' : 'android' — the same mapping the
button uses; the server accepts only web, ios, android, roku and tv, and the default is android).
Quick reference
| You want to… | Call |
|---|---|
| Set the SDK up | SubSovereign.configure(config) |
| See what the user unlocked | await checkEntitlements() → { hasAccess, … } |
| Load the published paywall | await getPaywallConfig(Platform.OS) or usePaywallConfig(params) |
| Draw it natively | <Paywall config onSelectProduct /> |
| Verify a Google purchase | await validateGooglePurchase({ purchaseToken, productId, accessLevelId }) |
| Verify an Apple purchase | validateApplePurchase on the Web/JS SDK |
| Record consent | await recordConsent({ purpose, granted }) |
| EU withdrawal | <WithdrawalButton /> · withdraw() · getWithdrawalConfig() |
Next steps
- Fuller data-layer walkthrough (flags, erasure, export): Web & React Native guide.
- New to the concepts? Read How SubSovereign works.