SubSovereign
All guides

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 userId per 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