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;
};| Type | Description |
|---|---|
RovenueConfig | Passed to configure() once at startup. |
User | The two IDs the SDK manages — permanent rovenueId and optional appUserId. |
Entitlement | A single access grant. Check isActive at the gate. |
Offerings | The product catalogue fetched from the store, structured by offering. |
PurchaseResult | Returned 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.
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.
import Rovenue
Rovenue.configure(
apiKey: "rov_pub_your_key_here"
)Call from application(_:didFinishLaunchingWithOptions:) (UIKit) or your @main init() (SwiftUI). See App lifecycle.
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:
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.
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();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()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.