Amba
SDKsFeatures

Experiments

Sticky A/B variants from every SDK — getVariant, trackExposure, the onExperimentAssigned hook for your analytics, and the React useExperiment hook.

Every SDK reads experiment variants with two calls. experiments.getVariant(key) assigns the signed-in user to the experiment and returns their variant. The first call creates the assignment; every later call returns the same variant, on any device the user signs in on. experiments.trackExposure(key) records that the user saw that variant. Define experiments and read their results from the admin API or MCP.

Both calls need a signed-in user (an anonymous session counts). Their routes are in the client API reference.

Quick start

import { Amba } from '@layers/amba-web';
 
const { variant } = await Amba.experiments.getVariant('paywall_copy');
showPaywall(variant === 'b' ? 'short' : 'long');
await Amba.experiments.trackExposure('paywall_copy');

getVariant(key)

Returns the assignment:

FieldMeaning
experimentKeyThe experiment's key.
variantThe variant key the user is bucketed into, for example control.
bucketingIdThe id Amba hashed to pick the variant: the signed-in user's id. Send it with the assignment to your analytics so its counts match Amba's.

Swift, Kotlin, Dart and C# name the fields the same way (ExperimentKey, Variant, BucketingId in C#).

getVariant throws when the user can't be assigned. The error's code tells you why:

CodeWhen
EXPERIMENT_NOT_FOUNDNo experiment has this key.
EXPERIMENT_INACTIVEThe experiment is paused or ended and this user has no assignment yet. Users assigned earlier keep getting their variant.
NO_VALID_VARIANTSThe experiment has no variant with a positive weight.

Render your control experience when getVariant throws.

trackExposure(key)

Records that the user saw their variant. Call it when the variant is on screen, which can be later than the getVariant call. Results count exposed users, so a user who was assigned and never saw the variant doesn't dilute a conversion rate. The user needs an assignment first; without one the call fails with ASSIGNMENT_REQUIRED.

Send assignments to your analytics

onExperimentAssigned calls your listener with { experimentKey, variant, bucketingId } the first time getVariant returns a user's variant for an experiment in a session, while the listener is subscribed. Later getVariant calls for the same user and experiment don't call it again. A session lasts until sign-out or until the app (or page) restarts, since the SDK keeps this record in memory: each launch reports each experiment again. The Node SDK keeps one record per AmbaClient for its most recent 10,000 user and experiment pairs.

The listener runs on the SDK's call path, right after getVariant gets its answer and before it returns, so keep it short. On Swift it runs on the task that called getVariant, which may be off the main thread. An error it throws is caught and logged, and never reaches the getVariant caller. A JavaScript listener's rejected promise is caught too. On Dart, a listener that returns a Future has its error caught. On C#, pass a Func<ExperimentAssignment, Task> for async work, and its faults are logged.

On the web, Node and React Native, the listener belongs to the SDK and keeps working across configure calls. On Swift, Kotlin, Flutter and Unity it belongs to the configured client: subscribe after configure, and again after a reset.

Forward each assignment to Layers as an experiment_assigned event with the properties experiment_key, variant and bucketing_id:

const unsubscribe = Amba.onExperimentAssigned((a) => {
  layers.track('experiment_assigned', {
    experiment_key: a.experimentKey,
    variant: a.variant,
    bucketing_id: a.bucketingId,
  });
});

Subscribe once, right after configure, so the listener sees the first assignment of each session.

One event per experiment per session

Your analytics receives one experiment_assigned event per user, experiment and session (a launch, or a sign-in within it), so you can call getVariant wherever you branch on the variant without adding events for every call.

React: useExperiment(key)

useExperiment(key) returns { variant, assignment, loading, error, refetch, trackExposure }. variant is null until the user's assignment loads, while no one is signed in (the hook makes no request then, and error stays null), and when the user can't be assigned. It re-resolves after every auth change: for the same user (a token refresh) the variant stays on screen, even if the re-resolve fails; a different user clears it until the new answer arrives.

Request headers for targeting

From these SDK versions, every request carries the app's version, the device's region and the device's operating system, so segments can target them:

HeaderValue
X-App-VersioniOS CFBundleShortVersionString, Android versionName (pass context to Amba.configure), Unity Application.version, Flutter and Expo from the native app info. Web and React Native send it when you pass appVersion to configure. Flutter reads it through the plugin, so call WidgetsFlutterBinding.ensureInitialized() before Amba.configure.
X-Device-RegionThe ISO 3166-1 alpha-2 region of the device locale, uppercase (US, FR).
X-Device-OSios, android, web, macos, windows, linux or other.

Each header is read once when the SDK starts and is left out when its value is unknown. X-App-Version carries the version up to its first space (2.3.1 (45) sends 2.3.1) and is left out when that part holds anything other than letters, digits and . _ + -. The Node SDK sends none of them.

await Amba.configure({ projectId, clientKey, appVersion: '2.3.1' }); // web, React Native
Amba.configure(apiKey = BuildConfig.AMBA_API_KEY, context = this) // Android

SDK versions with experiments and these headers: @layers/amba-web 4.1.0, @layers/amba-node 4.2.0, @layers/amba-react 4.1.0, @layers/amba-react-native 4.1.0, @layers/amba-expo 4.1.0, Swift and Kotlin 4.2.0, Flutter 4.1.0 and Unity 4.1.0.

On this page