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.
import type { RevTurbineUserContext } from '@revturbine/sdk';
export const user: RevTurbineUserContext = { id: 'usr_01J8M4X9Q2', plan_handle: 'starter', custom: { role: 'editor', company_size: 42, },};Choose the right entry point
Section titled “Choose the right entry point”| Situation | API | Behavior |
|---|---|---|
| Initialize React or the browser SDK | options.user | Sets the initial RevTurbineUserContext |
| A user logs in or you switch accounts | identify(userId, context?) | Establishes the identity, invalidates cached decisions, and applies identity-safe context fields |
| Data changes for the same user | update(patch) | Merges plan, usage, custom traits, and other supported session fields without changing identity |
| Merge a low-level context snapshot | setUserContext(context) | Merges a RevTurbineUserContext; React’s setContext calls this method |
| A user logs out | reset() | Clears the known identity and returns to inferred anonymous state |
| A demo or test needs a blank slate | resetUserContext() | Clears all user values, usage, decisions, and interaction history without anonymous inference |
| Inspect held SDK state | getUserContext() | Returns the current context with tenant_id, user_id, and persistence-envelope fields |
| Inspect what decisions actually read | getEffectiveUserContext() | 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:
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.
Field reference
Section titled “Field reference”Identity and account
Section titled “Identity and account”| Field | Type | Guidance |
|---|---|---|
id | string | Your stable, opaque user identifier. Supply it in options.user; pass it as the first argument to identify(). Avoid emails and other guessable identifiers |
account_id | string | null | The 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 |
email | string | null | Optional contact identity. Put email here, never in id |
email_type | string | Optional 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.
| Field | Type | Guidance |
|---|---|---|
plan_handle | string | Preferred 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:
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', },};Decision and presentation inputs
Section titled “Decision and presentation inputs”| Field | Type | What reads it |
|---|---|---|
custom | Record<string, JsonValue> | Customer-owned attributes for Segment targeting, such as role, region, or company size. Use string, number, or boolean values for predicates |
usage | Record<string, UserUsageEntry> | Usage-limit and credit entitlements. Keys are entitlement handles; see Report usage |
trial | RevTurbineTrialContext | Reverse-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 |
tiers | Record<string, string> | Current tier handle per capability-tier entitlement handle |
entitlements | Record<string, boolean> | Precomputed grants. Most apps should let the Playbook derive these rather than asserting them in the browser |
experiments | Record<string, string> | Experiment handle to variant handle assignments; see Use Experiment Assignments |
payment_failed | boolean | Hard-failure recovery placements |
payment_at_risk | boolean | Soft billing-attention placements |
instances | UserInstanceContext[] | Per-product-instance plans, usage, trials, and entitlements for multi-instance accounts |
personalization | Record<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.
Server SDK context shapes
Section titled “Server SDK context shapes”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:
| Concept | TypeScript headless | Python and Rust server facade |
|---|---|---|
| Identity | user.id; tenant is the top-level tenantId initialization option | tenant_id and user_id inside the facade object |
| Plan | plan_handle, with optional plan: { handle, name, ... } metadata | plan_handle, with optional plan_name; no plan object |
| Usage at construction | {handle: {entitlement_handle, unit, amount, limit?}} | {handle: {used, limit}} |
| Trial | trial | trial_status, containing already-derived trial state |
| Targeting | custom, experiments, and other canonical context fields | No raw-trait or experiment derivation; Python also accepts pre-resolved segment_ids for eligible plan and add-on helpers |
| Shared signals | payment_failed, payment_at_risk, and tiers | The same three fields |
See the detailed TypeScript server guide, Python guide, and Rust guide.
Report usage
Section titled “Report usage”Initial user context uses rich UserUsageEntry objects. The record key and
entitlement_handle both refer to the entitlement’s unique_handle:
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:
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.
Merge behavior
Section titled “Merge behavior”Context changes use upsert semantics:
- Omitted fields and fields set to
undefinedkeep their current values. setUserContext()mergescustom,entitlements,usage,personalization, andexperimentsby key; supplied keys win.update()applies the same merge behavior to its supported non-usage fields and routes its simple numericusagemap through the usage update path.update()cannot changeid. Callidentify(newId, context)to switch users.- An empty object does not erase a previously populated nested map. Use
reset()for logout orresetUserContext()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.
Add server-derived context
Section titled “Add server-derived context”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:
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.
SDK-managed TypeScript fields
Section titled “SDK-managed TypeScript fields”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, andupdated_atare added bygetUserContext()and the persistence layer.derived_config_version,context_hash, andderived_computed_atdescribe the server-computed entitlement cache.activity_scoreandactivity_score_computed_atare server-computed activity signals.
Keep application-specific values under custom; do not repurpose SDK-managed
fields.
Privacy and validation
Section titled “Privacy and validation”- Use an opaque internal id. If
identify()receives an email-shaped id, the SDK warns and tells you to use theemailfield instead. - Do not put secrets, access tokens, payment details, or raw provider records in user context.
- Keep Segment-targeted
customvalues scalar. Nested JSON may be accepted at the SDK boundary, but it is not a scalar Segment predicate. RevTurbineProvideroptions,identify(), andupdate()exact-check context objects even when they come through an intermediate variable. Plain JavaScript calls toidentify()andupdate()receive visible warnings for unknown keys, which are dropped rather than silently becoming traits.
Related
Section titled “Related”- Core Concepts — where user context fits in the decision loop
- React Integration — provider and hook setup
- Configuration Reference — initialization options
- Entitlements — how plan and usage become access decisions
- Run an Experiment — direct and provider-owned assignments
RevTurbineUserContextAPI reference — generated type details