Skip to main content

Exposure Events

The assignment moment

Exposure events happen every time the treatment function is called to check which variant of an experiment to show a visitor. This is how it might look in case of an A/B/C experiment.

context.ready().then(function () {
const expAssignment = context.treatment("experiment_name");
if (expAssignment === 1) {
// insert code to show for Variant 1
} else if (expAssignment === 2) {
// insert code to show for Variant 2
} else {
// insert the Control/Base code
}
});

This treatment function call will return the variant assignment. 0 for base, 1 for variant 1 or 2 for variant 2.

info

For a given user identifier, the treatment call will always return the same value, making the assignment sticky. This is a very important feature as this means the same visitor will always be shown the same variant.

Before sending exposure events, make sure to correctly initialise and enrich the ABsmartly's context with the visitor's identity.

The events page

As soon as your code is deployed and visitors enter this part of your product, exposure events will start being appearing in the ABsmartly's events' page.

Monitoring events on the events' page is a great way to ensure your tracking is set up properly.

tip

You can even start monitoring the events page before the experiment is started.

Understanding exposure events data

From the events' page, you can inspect any exposure event raw json data by clicking the event on the event's page.

{
"event_type": "exposure",
"unit_uid": "of2dhFlqHRXpW8iQG9mepw",
"unit_uid_hex": "a1fd9d84596a1d15e95bc8901bd99ea7",
"unit_type": "absId",
"unit_type_id": 42,
"agent": "absmartly-javascript-sdk",
"application": "absmartly.com",
"application_id": 39,
"environment": "Prod",
"environment_id": 3,
"environment_type": "production",
"event_at": 1762888120666,
"unit_attributes": {
"application": "absmartly.com",
"user_agent": {
"device_type": "Mobile",
"crawler": "no",
"value": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
"platform": "iOS",
"browser": "Safari",
"browser_type": "Browser"
},
"__crawler": false
},
"experiment_id": 3214,
"experiment_name": "absmartly_site_aa",
"experiment_iteration": 0,
"variant": 0,
"flags": 1,
"properties": null
}

Below is an overview of some of the data you will find in the exposure event's raw data.

FieldDescription
event_typeThis indicates the sort of event. In this case, exposure.
unit_typeThe sort of unit used to identify the visitor.
unit_uidThe visitor's unique identifier for this unit type. One event is sent for each register identifier.
applicationIndicates the application where this event comes from.
event_atThe event's timestamp.
unit_attributesThe list of visitor's attributes registered for this event.
experiment_idThe experiment's id.
experiment_nameThe experiment's name.
variantThe variant's assignment'. 0 for base. 1 for variant 1. etc.
flagsA bitmask describing how this exposure was assigned. See The flags field below.

The flags field

The flags field matters most when you export events and crunch the numbers yourself. Inside the UI it's already applied, so you rarely have to think about it. A raw events-page export gives you every exposure we ingested, and flags is how you tell a real participant apart from a bot, an ineligible visitor or an overridden assignment. An experiment export skips this step, since it's already filtered down to effective exposures, but any query you run against a raw export or your own warehouse needs it.

flags is a bitmask. Each bit answers one yes/no question about the exposure, all packed into a single integer, so one event can have several bits set at once. That means you test it with a bitwise AND rather than comparing the whole number.

The bits

Bit valueNameMeaning
1eligibleThe unit was eligible to participate. When this is 0, traffic allocation was below 100% and this unit fell outside it.
2assignedThe assignment logic ran for this exposure. When this is 0, no variant was actually assigned.
4overriddenThe assignment was overridden by the SDK.
8full onThe experiment was full on (100% on a single variant) when the event was ingested.
16customThe exposure used a custom assignment.
32audience mismatchThe unit did not match the experiment's intended audience.
64crawlerThe unit was detected as a bot or crawler.
128ignoredThe unit was flagged to be ignored via its unit attributes. Present in exported data but not shown in the UI.
256rule overrideThe variant was determined by a rule override rather than the normal assignment.

Reading a value

Take flags = 3. In binary that's 0000 0011, so bits 1 and 2 are set:

const eligible = (flags & 1) !== 0; // true
const assigned = (flags & 2) !== 0; // true
const crawler = (flags & 64) !== 0; // false

That's a clean exposure: the visitor was eligible and got assigned a variant. Compare it with flags = 66 (0100 0010), where bit 2 (assigned) and bit 64 (crawler) are set but bit 1 (eligible) isn't. That row is a bot, so it shouldn't count as a participant.

Matching the UI's participant count

A row counts as an effective participant when it's eligible and assigned and none of the disqualifying bits are set. You can check all of that in one masked test, (flags & 207) = 3: the mask keeps bits 1 + 2 + 4 + 8 + 64 + 128, and = 3 means eligible (1) and assigned (2) are the only ones left standing.

-- matches the UI's participant count for one experiment
SELECT count(DISTINCT unit_uid)
FROM exposures
WHERE (flags & 207) = 3
AND experiment_id = <your experiment id>
AND unit_type = '<your unit type>'

Scope the count to a single experiment and unit type, as shown above. A unit_uid is only unique within its unit_type, and an export can span many experiments, so counting across the whole file would merge separate visitors.

The 207 mask throws out overridden (4), full on (8), crawler (64) and ignored (128) rows. Rule overrides get excluded too, just by a different route: the SDK never marks them as assigned (bit 2), so they fail the = 3 test on their own. Custom (16) and audience mismatch (32) aren't in the mask, so on their own they won't drop a row from the count.

Events info & warning

Exposure events can be labelled with extra information or warning

LabelTypeTrigger
An experiment with this name was not running at the time of ingestionwarningexperiment_id empty (not flag-driven)
The unit of this exposure event was not eligible to participate in the experiment at the time of ingestion because traffic allocation for the experiment was < 100warningeligible bit (1) not set
The assignment logic did not run for this exposure eventwarningassigned bit (2) not set
The assignment of this exposure was overridden by the SDKwarningoverridden bit (4) set
The experiment was full on at the time of ingestioninfofull on bit (8) set
This exposure has a custom assignmentinfocustom bit (16) set
The unit of this exposure event did not match the intended audienceinfoaudience mismatch bit (32) set
The unit of this exposure event was detected to be a bot/crawlerinfocrawler bit (64) set
The variant of this exposure was determined by a rule overrideinforule override bit (256) set

The full on label is worth acting on: it's good practice to clean up full on experiments once you're done with them.