Skip to content

RevTurbineUserContext

RevTurbineUserContext is the user snapshot RevTurbine evaluates against the active Playbook. Entitlement checks, Segment targeting, placements, personalization, and experiment assignment all depend on this context.

For most integrations, start with a stable user id and the current plan handle. Add only the fields your Playbook reads.

TypeScript
import type { RevTurbineUserContext } from '@revturbine/sdk';
export const user: RevTurbineUserContext = {
id: 'usr_01J8M4X9Q2',
plan_handle: 'starter',
custom: {
role: 'editor',
company_size: 42,
},
};
SituationAPIBehavior
Initialize React or the browser SDKoptions.userSets the initial RevTurbineUserContext
A user logs in or you switch accountsidentify(userId, context?)Establishes the identity, invalidates cached decisions, and applies identity-safe context fields
Data changes for the same userupdate(patch)Merges plan, usage, custom traits, and other supported session fields without changing identity
Merge a low-level context snapshotsetUserContext(context)Merges a RevTurbineUserContext; React’s setContext calls this method
A user logs outreset()Clears the known identity and returns to inferred anonymous state
A demo or test needs a blank slateresetUserContext()Clears all user values, usage, decisions, and interaction history without anonymous inference
Inspect held SDK stategetUserContext()Returns the current context with tenant_id, user_id, and persistence-envelope fields
Inspect what decisions actually readgetEffectiveUserContext()Returns the immutable context after customer and experiment providers resolve

Use identify() whenever the identity changes. Use update() for later changes to the same user:

TypeScript
import type { RevTurbineCustomerSdk } from '@revturbine/sdk';
type SignedInUser = {
id: string;
accountId: string;
planHandle: string;
role: string;
};
export function syncSignedInUser(rt: RevTurbineCustomerSdk, user: SignedInUser) {
rt.identify(user.id, {
account_id: user.accountId,
plan_handle: user.planHandle,
custom: { role: user.role },
});
}
export function applyPlanChange(rt: RevTurbineCustomerSdk, planHandle: string) {
rt.update({ plan_handle: planHandle });
}
export function signOut(rt: RevTurbineCustomerSdk) {
rt.reset();
}

Context updates notify mounted hooks and components, so active entitlement checks and placements re-evaluate for the new snapshot.

The optional context passed to identify() accepts account_id, email, plan_handle, plan, usage, entitlements, custom, and personalization. Set trial, tier, billing-recovery, and instance changes at initialization or with update(). Supply direct experiments assignments at initialization, through an experiment provider, or through the lower-level setUserContext() merge.

FieldTypeGuidance
idstringYour stable, opaque user identifier. Supply it in options.user; pass it as the first argument to identify(). Avoid emails and other guessable identifiers
account_idstring | nullThe organization, workspace, or account the user is acting in. Optional — leave it unset and every event the SDK emits carries user-fallback:<user_id> instead. That prefix is the point: account_id is the join key RevTurbine’s account-grain analytics match on, so a bare user id stamped there is indistinguishable from a real account, while a prefixed one is countable and obviously fabricated (RevTurbine’s own account-grain readouts exclude it). Use isFallbackAccountId() to classify a value. Set account_id whenever your product has a notion of an account
emailstring | nullOptional contact identity. Put email here, never in id
email_typestringOptional classification such as business or personal

id is optional at the type boundary because the browser SDK can operate with anonymous state. Once your app knows the user, identify them with the same stable internal id on every device and session.

FieldTypeGuidance
plan_handlestringPreferred plan identity. It must equal the plan’s unique_handle in the Playbook, such as starter or pro
plan{ handle, name, price?, billing_period? }Optional display metadata. plan.handle is a fallback identity when plan_handle is absent; the flat field wins when both are present

Plan ids from your database or RevTurbine’s internal records do not belong in user context. plan.id is a retired shape and is rejected. Use the plan’s stable handle:

TypeScript
import type { RevTurbineUserContext } from '@revturbine/sdk';
export const professionalUser: RevTurbineUserContext = {
id: 'usr_01J8M4X9Q2',
plan_handle: 'pro',
plan: {
handle: 'pro',
name: 'Professional',
price: '$49',
billing_period: 'monthly',
},
};
FieldTypeWhat reads it
customRecord<string, JsonValue>Customer-owned attributes for Segment targeting, such as role, region, or company size. Use string, number, or boolean values for predicates
usageRecord<string, UserUsageEntry>Usage-limit and credit entitlements. Keys are entitlement handles; see Report usage
trialRevTurbineTrialContextReverse-trial entitlement grants, trial-lifecycle placements (trial_ending, trial_progress, trial_ended, trial_converted) and the {{trial_days_remaining}} / {{trial_days_total}} tokens. When a server-delivered or hydrated trial status is also present, the most recent change wins
tiersRecord<string, string>Current tier handle per capability-tier entitlement handle
entitlementsRecord<string, boolean>Precomputed grants. Most apps should let the Playbook derive these rather than asserting them in the browser
experimentsRecord<string, string>Experiment handle to variant handle assignments; see Use Experiment Assignments
payment_failedbooleanHard-failure recovery placements
payment_at_riskbooleanSoft billing-attention placements
instancesUserInstanceContext[]Per-product-instance plans, usage, trials, and entitlements for multi-instance accounts
personalizationRecord<string, string | number>Transient tokens used in placement copy. It is rebuilt for each SDK session and is not an access-control input

Browser values can be changed by the person running the browser. Use them for UI decisions, but re-run value-bearing entitlement checks in a server SDK over the same Playbook before spending money, provisioning seats, or granting sensitive access. See Client vs Server Enforcement.

The TypeScript headless entry point accepts this same canonical RevTurbineUserContext. The Python and Rust local evaluators instead expose smaller, request-scoped UserContext facade types. Map your application user into the shape for the SDK you are calling:

ConceptTypeScript headlessPython and Rust server facade
Identityuser.id; tenant is the top-level tenantId initialization optiontenant_id and user_id inside the facade object
Planplan_handle, with optional plan: { handle, name, ... } metadataplan_handle, with optional plan_name; no plan object
Usage at construction{handle: {entitlement_handle, unit, amount, limit?}}{handle: {used, limit}}
Trialtrialtrial_status, containing already-derived trial state
Targetingcustom, experiments, and other canonical context fieldsNo raw-trait or experiment derivation; Python also accepts pre-resolved segment_ids for eligible plan and add-on helpers
Shared signalspayment_failed, payment_at_risk, and tiersThe same three fields

See the detailed TypeScript server guide, Python guide, and Rust guide.

Initial user context uses rich UserUsageEntry objects. The record key and entitlement_handle both refer to the entitlement’s unique_handle:

TypeScript
import type { RevTurbineUserContext } from '@revturbine/sdk';
export const userWithUsage: RevTurbineUserContext = {
id: 'usr_01J8M4X9Q2',
plan_handle: 'pro',
usage: {
api_calls: {
entitlement_handle: 'api_calls',
unit: 'calls',
amount: 42,
limit: 1_000,
},
},
};

Runtime updates use the simpler Record<string, number> shape:

TypeScript
import type { RevTurbineCustomerSdk } from '@revturbine/sdk';
export function reportApiCallTotal(rt: RevTurbineCustomerSdk, total: number) {
rt.update({ usage: { api_calls: total } });
}

Usage numbers are absolute balances, not deltas. If the user has made 43 API calls, report 43, not 1. A usage key that does not match an entitlement handle is stored but cannot affect a rule; once a Playbook is loaded, the SDK warns about unmatched keys.

Context changes use upsert semantics:

  • Omitted fields and fields set to undefined keep their current values.
  • setUserContext() merges custom, entitlements, usage, personalization, and experiments by key; supplied keys win.
  • update() applies the same merge behavior to its supported non-usage fields and routes its simple numeric usage map through the usage update path.
  • update() cannot change id. Call identify(newId, context) to switch users.
  • An empty object does not erase a previously populated nested map. Use reset() for logout or resetUserContext() when you need a completely blank context.

Treat the object you pass to RevTurbine as immutable. Create a new object for each meaningful change instead of mutating an earlier value in place.

Some signals should come from a trusted backend: the plan last updated by a Stripe webhook, trial status, or coarse payment health. Supply a clientSession callback and the SDK will fetch the signed-in user’s client-safe context automatically:

TypeScript
import { initRevTurbine } from '@revturbine/sdk';
export const rt = initRevTurbine({
tenantId: 'tenant_abc',
publicKey: 'rtk_…', // public key — safe in the browser
user: { id: 'user_123', plan_handle: 'free' },
clientSession: () =>
fetch('/api/revturbine/client-session', { method: 'POST' })
.then((response) => {
if (!response.ok) throw new Error('Could not create RevTurbine client session');
return response.json();
})
.then((body) => body.client_token),
});

Your backend—not the browser—mints the short-lived rt_client_… token. The SDK requests one when needed, requests a fresh token after identify(), and retries with a new token after expiry. Server-derived plan and trial state then join the effective user context used for local evaluation. If enrichment is unavailable, the SDK keeps using the context your app supplied.

See Configuration Reference: server-derived context for the complete token flow.

The broader generated TypeScript user schema also contains persistence and cache metadata. Application code using RevTurbineUserContext should not author these fields:

  • tenant_id, user_id, created_at, and updated_at are added by getUserContext() and the persistence layer.
  • derived_config_version, context_hash, and derived_computed_at describe the server-computed entitlement cache.
  • activity_score and activity_score_computed_at are server-computed activity signals.

Keep application-specific values under custom; do not repurpose SDK-managed fields.

  • Use an opaque internal id. If identify() receives an email-shaped id, the SDK warns and tells you to use the email field instead.
  • Do not put secrets, access tokens, payment details, or raw provider records in user context.
  • Keep Segment-targeted custom values scalar. Nested JSON may be accepted at the SDK boundary, but it is not a scalar Segment predicate.
  • RevTurbineProvider options, identify(), and update() exact-check context objects even when they come through an intermediate variable. Plain JavaScript calls to identify() and update() receive visible warnings for unknown keys, which are dropped rather than silently becoming traits.