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()
};| Field | Type | Description |
|---|---|---|
rovenueId | string | Permanent per-install identifier (prefix rov_); never changes for the lifetime of the install. |
appUserId | string | null | Your 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 calledval user = Rovenue.shared.currentUser()
println(user.rovenueId) // always present, e.g. "rov_01abc..."
println(user.appUserId ?: "(anonymous)") // null until identify() has been calledCall 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_ idAfter 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.