SubSovereign on Web & React Native — integration guide
This guide covers the JavaScript/TypeScript SDK, which works in three places from one library: React Native (iOS + Android), React Native TV, and the web (with Stripe). It takes your app from "I have no idea who paid me" to "my app unlocks the right features for the right user, verified on my own server."
What SubSovereign does for you
Your users subscribe through an app store (Apple, Google) or, on the web, through Stripe. SubSovereign answers one question for your app, reliably: what has this user actually paid for?
- Your app asks SubSovereign for the user's entitlement — the access they've unlocked.
- The receipt validation happens on the server, directly with the store (or Stripe), so a tampered client can't fake a subscription.
- The paywall is configured remotely, so you can change prices, trials, and wording without a redeploy.
- It is self-hosted: it runs on your infrastructure, your users' data stays with you, and there is no revenue share — you keep 100% of what your users pay.
You never trust the client. The client asks; the server decides.
Before you start
You'll need a running SubSovereign server, an app registered in the dashboard (giving you an
appId and an API key), and your access levels (tiers, e.g. pro) created and linked to
the products your users buy — App Store / Google Play products for React Native, or Stripe prices
for web. Handle the purchase itself as you normally would (in-app purchase libraries on React Native,
Stripe Checkout/Billing on web); SubSovereign verifies and records it afterwards.
Step 1 — Install the SDK
npm install @subsovereign/js-sdk
import SubSovereign from '@subsovereign/js-sdk';
Step 2 — Configure once, when your app starts
SubSovereign.configure({
apiKey: 'YOUR_APP_API_KEY', // from the dashboard
appId: 'your-app-id', // from the dashboard
baseUrl: 'https://subs.yourdomain.com/api/v1', // YOUR self-hosted server
userId: currentUser.id, // your own stable user id
locale: 'en', // the user's language
});
Use a stable userId for the signed-in user — the same value everywhere, so access follows them
across devices and platforms. Re-run configure if a different user signs in.
Step 3 — Check what the user can access
The full picture comes from checkEntitlements():
try {
const result = await SubSovereign.checkEntitlements();
if (result.hasAccess) unlockProFeatures();
else showFreeExperience();
} catch (err) {
// Keep the user on their last-known access and retry later.
console.warn('Entitlement check failed:', err);
}
For a simple gate there's a convenience helper, hasAccess(), which fails closed (returns false)
on a network error so a blip never accidentally unlocks paid features:
if (await SubSovereign.hasAccess('pro')) unlockProFeatures();
Each entitlement in result.entitlements carries accessLevelId, isActive, expiresAt,
willRenew, and its store. result.fromCache is true if the answer came from the last-known
cache during a brief outage.
Step 4 — Sell a subscription
Show the paywall
const paywall = await SubSovereign.getPaywallConfig('web'); // or 'ios' | 'android' | 'firetv' | 'roku'
if (paywall) renderPaywall(paywall); // headline, features, products…
else renderFallbackPaywall();
Complete the purchase, then verify it
Run the purchase in the usual way for the platform, then hand the result to SubSovereign so the server validates it and grants the access level. Pick the call that matches where the purchase happened:
// Web (Stripe)
await SubSovereign.validateStripeSubscription({
subscriptionId, productId, accessLevelId: 'pro',
});
// React Native — iOS (StoreKit)
await SubSovereign.validateApplePurchase({ transactionId, productId, accessLevelId: 'pro' });
// React Native — Android (Play Billing)
await SubSovereign.validateGooglePurchase({ purchaseToken, productId, accessLevelId: 'pro' });
Each returns true once the server has confirmed the purchase. Re-run checkEntitlements()
afterwards and unlock.
Feature flags
Roll features out from the server without a deploy:
const flags = await SubSovereign.getFeatureFlags(); // { newPlayer: true, ... }
if (flags.newPlayer) showNewPlayer();
Step 5 — Privacy: GDPR and CCPA
await SubSovereign.recordConsent({ purpose: 'analytics', granted: true });
await SubSovereign.requestErasure(); // "forget me"
const myData = await SubSovereign.exportMyData(); // data export
await SubSovereign.setDoNotSell(true); // CCPA "Do Not Sell" / GPC signal
Handling errors
checkEntitlements and the validate… calls throw on failure — wrap them in try/catch and, on
error, keep the user on their last-known access and retry later; never lock a paying user out over a
blip. The convenience helpers (hasAccess, getPaywallConfig, getFeatureFlags) instead fail
closed, returning false/null/{}, so they're safe to call inline.
Best practices
- Check on mount / launch so gating is right before the user reaches a locked feature.
- Re-check after buying so the UI updates immediately.
- Never trust the client — ask the server; it verified the receipt.
- One
userIdper real user, kept stable across web and mobile.
Quick reference
| You want to… | Call |
|---|---|
| Set the SDK up | SubSovereign.configure(config) |
| See what the user unlocked | await checkEntitlements() → EntitlementResult |
| Simple gate (fail-closed) | await hasAccess('pro') → boolean |
| Show the remote paywall | await getPaywallConfig(platform) → PaywallConfig | null |
| Verify a Stripe / Apple / Google purchase | await validateStripeSubscription / validateApplePurchase / validateGooglePurchase(…) |
| Read feature flags | await getFeatureFlags() |
| GDPR / CCPA | recordConsent · requestErasure · exportMyData · setDoNotSell |
Next steps
- Native platforms have their own guides: Android, iOS, Roku.
- New to the concepts? Read How SubSovereign works.