Run an Experiment
RevTurbine can allocate a native experiment or consume the assignment made by the experimentation SDK already running in your application. For an external assignment, your application owns and initializes the vendor client. A small RevTurbine domain provider maps the vendor’s keys to canonical RevTurbine experiment and variant handles.
The normalized assignment becomes part of an ephemeral effective user context. Segments, entitlements, placements, direct variant reads, and telemetry all use that same resolved assignment. RevTurbine never persists the vendor client or its credentials and never mutates the user object supplied by your app.
The flow
Section titled “The flow”- In the experiment editor, choose the assignment source: native (RevTurbine assigns) or Customer SDK provider (your vendor SDK assigns).
- Native: register the built-in bucketer (next section). Vendor: initialize the vendor SDK as usual and create a RevTurbine adapter with explicit experiment and variant mappings.
- Pass the provider in
domainProviderswhen initializing RevTurbine. - Use
getExperimentVariant()when application code needs the selected variant directly; RevTurbine decisions and telemetry already use the same assignment.
Native allocation
Section titled “Native allocation”No vendor SDK needed. Register the built-in bucketer as a domain provider. It assigns deterministically per subject, so the same user lands in the same arm on every load, and everything downstream — targeting, placements, getExperimentVariant(), telemetry — reads that one assignment.
import { initRevTurbine, createBasicExperimentProvider } from '@revturbine/sdk';
const rt = initRevTurbine({ tenantId: 'tenant_abc', apiKey: 'rtk_…', // publishable ingest key endpoint: 'https://revturbine.com/app', mode: 'snippet', domainProviders: [ createBasicExperimentProvider({ subject: () => currentUserId, // re-read on every resolve experiments: { pricing_test: { variants: { control: 1, treatment: 1 } }, // relative weights onboarding_copy: { variants: ['a', 'b'], exposure: 0.2 }, // 20% enrolled }, }), ],});A subject outside exposure is not enrolled — the experiment is simply absent from their assignment, which is different from being in control. No stable subject, no assignment: bucketing an anonymous id would hand the same person a different arm on the next load.
An allocation authored in the dashboard — an Experiment version with its handle, traffic allocation and weighted variants — drives createNativeExperimentAssignmentProvider instead, which also emits the assignment fact that ties results back to that version. See the API reference for its options.
GrowthBook
Section titled “GrowthBook”The GrowthBook adapter accepts an initialized customer-owned client. It is a separate optional entry, so GrowthBook is not included when your application imports only the base SDK.
import { GrowthBook } from '@growthbook/growthbook';import { initRevTurbine } from '@revturbine/sdk';import { createGrowthBookExperimentAssignmentProvider } from '@revturbine/sdk/growthbook';
const userId = 'user_123';const growthbook = new GrowthBook({ attributes: { id: userId },});
await growthbook.init({ apiHost: 'https://cdn.growthbook.io', clientKey: 'sdk-...',});
const experimentProvider = createGrowthBookExperimentAssignmentProvider({ client: growthbook, bindings: [{ experimentHandle: 'pricing_test', featureKey: 'pricing-variant', allowedVariants: ['control', 'treatment'], }],});
const rt = initRevTurbine({ tenantId: 'tenant_abc', publicKey: 'rtk_…', // public key — safe in the browser user: { id: userId, plan_handle: 'free' }, domainProviders: [experimentProvider],});Keep the GrowthBook attributes current using the GrowthBook client. When the
user changes in RevTurbine, identify() invalidates the effective context and
resolves the provider for the new identity.
Optimizely
Section titled “Optimizely”The Optimizely adapter also accepts an initialized client and derives the Optimizely user id from the current RevTurbine context by default.
import { createInstance } from '@optimizely/optimizely-sdk';import { initRevTurbine } from '@revturbine/sdk';import { createOptimizelyExperimentAssignmentProvider } from '@revturbine/sdk/optimizely';
const optimizely = createInstance({ sdkKey: 'your-optimizely-sdk-key' });await optimizely.onReady();
const experimentProvider = createOptimizelyExperimentAssignmentProvider({ client: optimizely, bindings: [{ experimentHandle: 'pricing_test', experimentKey: 'pricing-experiment', allowedVariants: ['control', 'treatment'], }], attributes: ({ userContext }) => ({ plan: userContext.plan_handle, }),});
const rt = initRevTurbine({ tenantId: 'tenant_abc', publicKey: 'rtk_…', // public key — safe in the browser user: { id: 'user_123', plan_handle: 'free' }, domainProviders: [experimentProvider],});Mappings are explicit: vendor keys do not leak into Playbooks. An allowed variant list prevents a misspelled or newly introduced vendor value from being silently treated as a valid RevTurbine variant.
Read the selected variant
Section titled “Read the selected variant”Use the structured selection when your application must render variant-specific behavior itself:
const selection = await rt.getExperimentVariant('pricing_test');
if (selection.status === 'assigned') { renderPricingVariant(selection.variantHandle);} else { // Missing, unavailable, conflicting, and unsupported assignments are never // fabricated as "control". Keep the existing safe experience. renderDefaultPricing();}The result distinguishes assigned, not_assigned, unavailable, and
unsupported. This lets you keep non-enrollment separate from vendor failure
and diagnose ownership or assignment conflicts without guessing.
For diagnostics, the same immutable context used by local evaluation is
available from getEffectiveUserContext():
const effectiveContext = await rt.getEffectiveUserContext();console.log(effectiveContext.experiments?.pricing_test);Supply an assignment directly
Section titled “Supply an assignment directly”An adapter is optional when your code has already normalized the assignment.
Use the canonical experiments map, not an arbitrary custom trait:
const rt = initRevTurbine({ tenantId: 'tenant_abc', publicKey: 'rtk_…', // public key — safe in the browser user: { id: 'user_123', plan_handle: 'free', experiments: { pricing_test: 'treatment' }, },});Direct and provider assignments coalesce when they agree. If they disagree, RevTurbine reports an assignment conflict and does not invent a winner. Two providers also cannot own the same experiment handle.
Target decisions and measure outcomes
Section titled “Target decisions and measure outcomes”Create a Segment for the experiment in the RevTurbine dashboard (Targeting Studio) and reference it from an entitlement rule or placement payload. The SDK resolves the provider before evaluating the Playbook, so experiment-backed Segment membership and the resulting placement or entitlement use the effective assignment.
Track the conversion normally:
await rt.track('upgrade_completed', { plan: 'professional', source: 'pricing_experiment',});The SDK stamps the effective experiment assignment on telemetry from the same resolution used by direct selection and local decisions. It does not call the vendor separately for each consumer.
Migrating from custom traits
Section titled “Migrating from custom traits”If an existing integration stores a variant under
custom.pricing_experiment, move it to either a client provider binding or
experiments.pricing_test. Update the Segment to use the canonical experiment
handle, validate the vendor’s variant vocabulary, and then remove the old
custom-trait predicate. Do not configure an Experiment Data Connection for
client assignment.
Related
Section titled “Related”- Placements — how placement payloads, targeting, and priority resolve
- Entitlements — entitlement rules and precedence
- Events & Analytics — signals used to measure the experiment