Rovenue SDK
Guides

Identifying Users

Understand the two-identifier model and how to attach your backend user ID to the current install.

Every Rovenue install carries two identifiers: a permanent SDK-generated rovenueId and an optional appUserId that you supply when your user signs in. Purchases, virtual currencies, and entitlements always belong to the rovenueId; the appUserId is a label you attach so your backend can address the same subscriber regardless of which device they are on.

The identity model is optimistic: identify() updates local state instantly and fires a background network call to bind the IDs server-side. The user never waits for a round trip. See Methods — Identity.

Types on this page

type User = {
  rovenueId: string;        // permanent per-install id (prefix rov_); always present
  appUserId: string | null; // your backend user id; null until identify()
};
FieldTypeDescription
rovenueIdstringPermanent per-install identifier (prefix rov_); never changes for the lifetime of the install.
appUserIdstring | nullYour backend's user ID; null until identify() is called.

Swift and Kotlin mirror these with platform-idiomatic names (user.rovenueId / user.appUserId). See Types — User.


Identity lifecycle

Read the current user

currentUser() is a cache read — it never hits the network and returns instantly. Use it to check whether a user is already identified at startup, or use the React Native useCurrentUser() hook for reactive rendering.

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

// Imperative cache read
const user = Rovenue.currentUser();
console.log(user.rovenueId);  // always present, e.g. "rov_01abc..."
console.log(user.appUserId);  // null until identify() has been called

// Reactive hook alternative — re-renders when identity changes
function Header() {
  const user = useCurrentUser();
  return <Text>{user?.appUserId ?? '(anonymous)'}</Text>;
}
let user = await Rovenue.shared.currentUser()
print(user.rovenueId)               // always present, e.g. "rov_01abc..."
print(user.appUserId ?? "(anonymous)") // nil until identify() has been called
val user = Rovenue.shared.currentUser()
println(user.rovenueId)                    // always present, e.g. "rov_01abc..."
println(user.appUserId ?: "(anonymous)")   // null until identify() has been called

Call identify() after sign-in

Call identify(appUserId) immediately after your auth flow completes and you have a stable backend user ID. The call is optimistic — local state updates immediately so entitlement reads are consistent without waiting for the server. In the background, Rovenue fires POST /v1/identify to bind the appUserId to the current rovenueId.

If that appUserId already exists on another install, Rovenue automatically transfers that install's purchases and virtual-currency balances to this device. No extra call is required.

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

// Call immediately after your auth flow resolves
await Rovenue.identify('user_abc123');

// The local user now reflects the new appUserId
const user = Rovenue.currentUser();
console.log(user.appUserId); // "user_abc123"
// Call immediately after your auth flow resolves
try await Rovenue.shared.identify("user_abc123")

// The local user now reflects the new appUserId
let user = await Rovenue.shared.currentUser()
print(user.appUserId ?? "nil") // "user_abc123"
// Call immediately after your auth flow resolves (suspend function)
Rovenue.shared.identify("user_abc123")

// The local user now reflects the new appUserId
val user = Rovenue.shared.currentUser()
println(user.appUserId) // "user_abc123"

Use opaque, unguessable IDs for appUserId — never an email address, username, or any value a third party could predict. Because identify() can transfer purchases between installs, a guessable ID is an account-takeover surface: an attacker who knows (or can guess) a victim's ID can call identify on their own device and claim the victim's entitlements.

Call logOut() on sign-out

When your user signs out, call logOut(). This mints a fresh rovenueId for the install and clears appUserId and all scope-bound caches. The next user on a shared device starts with a clean slate and cannot inherit any prior entitlements or virtual-currency balances.

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

// Call when your user signs out
await Rovenue.logOut();

const user = Rovenue.currentUser();
console.log(user.appUserId);  // null — fresh anonymous install
console.log(user.rovenueId);  // new rov_ id, different from the previous one
// Call when your user signs out
try await Rovenue.shared.logOut()

let user = await Rovenue.shared.currentUser()
print(user.appUserId ?? "nil")  // nil — fresh anonymous install
print(user.rovenueId)           // new rov_ id
// Call when your user signs out (suspend function)
Rovenue.shared.logOut()

val user = Rovenue.shared.currentUser()
println(user.appUserId)   // null — fresh anonymous install
println(user.rovenueId)   // new rov_ id

After logOut() the SDK emits an IdentityChanged change event. If you have listeners attached via addChangeListener, they fire automatically. See Reacting to changes.


Backend-driven consolidation

For authoritative, bulk, or programmatic merging — for example, importing subscribers from another platform or resolving a support-driven account merge — your backend can call the transfer endpoint directly using your secret API key:

POST /v1/subscribers/transfer
Authorization: Bearer <secret-api-key>
Content-Type: application/json

{
  "fromRovenueId": "rov_01abc...",
  "toRovenueId":   "rov_01xyz..."
}

This call is server-to-server only. It gives you full control over which subscriber "wins" and is the recommended path for bulk migrations or cases where the client-side auto-transfer behaviour is insufficient.

Never put your secret API key (rov_sec_…) in the app binary. The transfer endpoint is a server-to-server call only. Expose a lightweight endpoint in your own backend that verifies the user session and then calls Rovenue on their behalf.


On this page