Rovenue SDK
Getting Started

Quick Start

Configure the SDK, check an entitlement, fetch offerings, make a purchase, and identify a logged-in user — end to end in one page.

This page walks through the four actions every Rovenue integration needs. Each step is self-contained — you can copy it straight into your project.

If you have not installed the SDK yet, start with Installation.


Types on this page

type RovenueConfig = {
  apiKey: string;      // public SDK key — prefix rov_pub_
  baseUrl?: string;    // API endpoint — defaults to https://api.rovenue.io
  logLevel?: 'off' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
  appVersion?: string;
};

type User = {
  rovenueId: string;        // permanent per-install id, prefix rov_
  appUserId: string | null; // your backend user id; null until identify()
};

type Entitlement = {
  id: string;                // entitlement id as configured in the dashboard
  isActive: boolean;         // true = access is currently granted
  productIdentifier: string; // the store SKU that unlocked it
  store: string;             // "app_store" | "play_store" | "stripe"
  expiresIso?: string;       // ISO-8601 expiry; undefined for lifetime products
};

type Offerings = {
  current: Offering | null;         // the default offering for this user
  all: Record<string, Offering>;    // all offerings keyed by identifier
};

type Offering = {
  identifier: string;
  isDefault: boolean;
  packages: Package[];
};

type Package = {
  identifier: string;   // e.g. "$rc_monthly", "$rc_annual"
  product: StoreProduct;
};

type StoreProduct = {
  id: string;
  type: 'subscription' | 'consumable' | 'non_consumable';
  displayName: string;
  priceString: string | null; // localised price string, e.g. "$4.99"
  price: number | null;
  currencyCode: string | null;
};

type PurchaseResult = {
  entitlements: Entitlement[];              // all active entitlements after the purchase
  virtualCurrencies: Record<string, number>; // virtual-currency balances after the purchase
  productId: string;
  storeTransactionId: string;
};
TypeDescription
RovenueConfigPassed to configure() once at startup.
UserThe two IDs the SDK manages — permanent rovenueId and optional appUserId.
EntitlementA single access grant. Check isActive at the gate.
OfferingsThe product catalogue fetched from the store, structured by offering.
PurchaseResultReturned by purchase() after server-side validation succeeds.

Swift and Kotlin expose the same fields with platform-idiomatic names. See Types.


Configure once at startup

Call configure() before any other SDK method — in your app entry point, before the UI renders. Pass your public API key. baseUrl is optional and defaults to the hosted endpoint https://api.rovenue.io.

index.ts
import { Rovenue } from '@rovenue/react-native-sdk';

Rovenue.configure({
  apiKey: 'rov_pub_your_key_here',
});

For Expo Router, put this in app/_layout.tsx before <Stack /> renders.

AppDelegate.swift
import Rovenue

Rovenue.configure(
  apiKey: "rov_pub_your_key_here"
)

Call from application(_:didFinishLaunchingWithOptions:) (UIKit) or your @main init() (SwiftUI). See App lifecycle.

App.kt
import dev.rovenue.sdk.Rovenue

Rovenue.configure(
  context = this,
  apiKey = "rov_pub_your_key_here"
)

Call from Application.onCreate(). See App lifecycle.

Self-hosting? Pass baseUrl to point the SDK at your own deployment, e.g. RN Rovenue.configure({ apiKey: 'rov_pub_...', baseUrl: 'https://api.acme.com' }), Swift Rovenue.configure(apiKey: "rov_pub_...", baseUrl: "https://api.acme.com"), Kotlin Rovenue.configure(apiKey = "rov_pub_...", baseUrl = "https://api.acme.com"). The host must use https:// — plain http:// is accepted only for localhost during local development.

Call configure() once at app startup, before any other Rovenue call. Calling it again later is supported — it replaces the shared instance — but calling any other method before the first configure() has run is a programming error to fix at the call site, not something to catch: on iOS it's a hard crash, and on Android it throws a plain (non-RovenueException) IllegalStateException. See Calling the SDK before configure() for the full per-platform breakdown.

See Configuring for all RovenueConfig options, including logLevel, appVersion, and self-hosted baseUrl values.

Check an entitlement

All entitlement reads are cache-first — they return instantly from the on-device SQLite store and never block on the network. The SDK keeps the cache warm in the background while the app is foregrounded.

Use the reactive hook in any component. It re-renders automatically whenever the entitlement changes:

ProBadge.tsx
import { useEntitlement } from '@rovenue/react-native-sdk';

export function ProBadge() {
  const pro = useEntitlement('pro'); // Entitlement | null

  if (!pro?.isActive) return null;
  return <View><Text>Pro</Text></View>;
}

Or read imperatively (outside a component):

import { Rovenue } from '@rovenue/react-native-sdk';

const pro = Rovenue.entitlement('pro'); // Entitlement | null
if (pro?.isActive) {
  // grant access
}
import Rovenue

// Async read — resolves from cache, never suspends on network
let pro = await Rovenue.shared.entitlement("pro")

if pro?.isActive == true {
  // grant access
}

In SwiftUI you can observe entitlements via the change stream. See Reacting to changes.

import dev.rovenue.sdk.Rovenue

val pro = Rovenue.shared.entitlement("pro")

if (pro?.isActive == true) {
  // grant access
}

Collect the SharedFlow<ChangeEvent> for reactive UI updates. See Reacting to changes.

Always check the isActive boolean — not expiresIso. An entitlement with a future expiry where isActive is false is in grace-period or revoked state. See Entitlements for the full state model.

The Entitlement type and its fields are documented at /docs/reference/types#entitlement. The method reference is at /docs/reference/methods#entitlement.

Fetch offerings and make a purchase

getOfferings() returns the product catalogue configured in your dashboard, with localised pricing from the store. purchase(pkg) drives the native purchase sheet (StoreKit 2 on iOS, Play Billing 6 on Android) and validates the receipt server-side — all in one call.

purchase() resolves only after server-side validation succeeds. The returned PurchaseResult contains up-to-date entitlements and virtualCurrencies — no polling needed.

PaywallScreen.tsx
import { Rovenue, RovenueError } from '@rovenue/react-native-sdk';

async function handlePurchase() {
  // 1. Fetch the current offering (cached; network-refreshed in background)
  const offerings = await Rovenue.getOfferings();
  const pkg = offerings.current?.packages[0];
  if (!pkg) return; // no products configured

  try {
    // 2. Launch native purchase sheet; resolves after server validation
    const result = await Rovenue.purchase(pkg);

    if (result.isDeferred) {
      // Ask-to-Buy / deferred payment — not an error; inform the user to wait
      return;
    }

    console.log('Entitlements:', result.entitlements);
    console.log('Virtual currencies:', result.virtualCurrencies);
  } catch (e) {
    if (e instanceof RovenueError && e.kind === 'PurchaseCanceled') {
      // User dismissed the sheet — not an error; do nothing
    } else {
      throw e;
    }
  }
}

// Restore purchases (call on a "Restore" button)
await Rovenue.restorePurchases();
PurchaseView.swift
import Rovenue

func handlePurchase() async {
  // 1. Fetch the current offering
  guard let offerings = try? await Rovenue.shared.getOfferings(),
        let pkg = offerings.current?.packages.first else { return }

  do {
    // 2. Launch native purchase sheet; resolves after server validation
    let result = try await Rovenue.shared.purchase(pkg)

    if result.isDeferred {
      // Ask-to-Buy / deferred — not an error; inform user
      return
    }

    print("Entitlements:", result.entitlements)
    print("Virtual currencies:", result.virtualCurrencies)
  } catch let e as RovenueError {
    switch e.kind {
    case .purchaseCanceled:
      break // User dismissed — not an error
    default:
      throw e
    }
  }
}

// Restore purchases
try? await Rovenue.shared.restorePurchases()
PurchaseViewModel.kt
import android.app.Activity
import dev.rovenue.sdk.Rovenue
import dev.rovenue.sdk.RovenueException
import dev.rovenue.sdk.ErrorKind

// `activity` is your current Activity — Play Billing requires it.
suspend fun handlePurchase(activity: Activity) {
  // 1. Fetch the current offering
  val offerings = Rovenue.shared.getOfferings()
  val pkg = offerings.current?.packages?.firstOrNull() ?: return

  try {
    // 2. Launch native purchase sheet; resolves after server validation
    val result = Rovenue.shared.purchase(activity, pkg)

    if (result.isDeferred) {
      // Deferred payment — not an error; inform user
      return
    }

    println("Entitlements: ${result.entitlements}")
    println("Virtual currencies: ${result.virtualCurrencies}")
  } catch (e: RovenueException) {
    when (e.kind) {
      ErrorKind.PURCHASE_CANCELED -> { /* User dismissed — not an error */ }
      else -> throw e
    }
  }
}

// Restore purchases (also needs the current Activity)
Rovenue.shared.restorePurchases(activity)

On purchase success, the SDK automatically refreshes entitlements and virtual-currency balances and emits a ChangeEvent. Any useEntitlement() hook or change listener updates without extra code. See Reacting to changes and Processing purchases.

Method references: getOfferings, purchase, restorePurchases. Type references: Offerings, Package, PurchaseResult.

Identify the user after login

The SDK starts every install in anonymous mode with a stable rovenueId. Once your user logs in, call identify(appUserId) to bind your backend user ID to the current install. Rovenue automatically transfers any anonymous purchases and credits to the identified account.

import { Rovenue } from '@rovenue/react-native-sdk';

// After your auth flow resolves and you have a stable user ID:
async function onLoginSuccess(userId: string) {
  Rovenue.identify(userId); // optimistic: local state updates immediately

  const user = Rovenue.currentUser();
  // user.rovenueId  → permanent install id (rov_...)
  // user.appUserId  → userId you just passed
}

// On sign-out: clear appUserId and mint a fresh rovenueId
async function onLogOut() {
  Rovenue.logOut();
}
import Rovenue

// After login:
func onLoginSuccess(userId: String) {
  Rovenue.shared.identify(appUserId: userId) // optimistic

  let user = await Rovenue.shared.currentUser()
  // user.rovenueId  → permanent install id (rov_...)
  // user.appUserId  → userId
}

// On sign-out:
func onLogOut() {
  Rovenue.shared.logOut()
}
import dev.rovenue.sdk.Rovenue

// After login:
fun onLoginSuccess(userId: String) {
  Rovenue.shared.identify(appUserId = userId) // optimistic

  val user = Rovenue.shared.currentUser()
  // user.rovenueId  → permanent install id (rov_...)
  // user.appUserId  → userId
}

// On sign-out:
fun onLogOut() {
  Rovenue.shared.logOut()
}

identify() is not how the user logs into your app — it is how you bind your backend's user ID to the Rovenue install. Call it only after your own auth flow completes and you have a stable, permanent user ID. Passing a temporary or session-scoped ID creates a new subscriber record per login and cannot be undone. See Identifying users and Identity & consent for guidance.

Method references: identify, logOut, currentUser. Type reference: User.

You're set up. For a deeper understanding of how the two user IDs relate, how the cache-first model works, and what the ChangeEvent stream does, read Core Concepts. For a full API reference, see Methods and Types.

On this page