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.
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.
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.
| Field | Description |
|---|---|
| event_type | This indicates the sort of event. In this case, exposure. |
| unit_type | The sort of unit used to identify the visitor. |
| unit_uid | The visitor's unique identifier for this unit type. One event is sent for each register identifier. |
| application | Indicates the application where this event comes from. |
| event_at | The event's timestamp. |
| unit_attributes | The list of visitor's attributes registered for this event. |
| experiment_id | The experiment's id. |
| experiment_name | The experiment's name. |
| variant | The variant's assignment'. 0 for base. 1 for variant 1. etc. |
| flags | A 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 value | Name | Meaning |
|---|---|---|
1 | eligible | The unit was eligible to participate. When this is 0, traffic allocation was below 100% and this unit fell outside it. |
2 | assigned | The assignment logic ran for this exposure. When this is 0, no variant was actually assigned. |
4 | overridden | The assignment was overridden by the SDK. |
8 | full on | The experiment was full on (100% on a single variant) when the event was ingested. |
16 | custom | The exposure used a custom assignment. |
32 | audience mismatch | The unit did not match the experiment's intended audience. |
64 | crawler | The unit was detected as a bot or crawler. |
128 | ignored | The unit was flagged to be ignored via its unit attributes. Present in exported data but not shown in the UI. |
256 | rule override | The 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
| Label | Type | Trigger |
|---|---|---|
| An experiment with this name was not running at the time of ingestion | warning | experiment_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 < 100 | warning | eligible bit (1) not set |
| The assignment logic did not run for this exposure event | warning | assigned bit (2) not set |
| The assignment of this exposure was overridden by the SDK | warning | overridden bit (4) set |
| The experiment was full on at the time of ingestion | info | full on bit (8) set |
| This exposure has a custom assignment | info | custom bit (16) set |
| The unit of this exposure event did not match the intended audience | info | audience mismatch bit (32) set |
| The unit of this exposure event was detected to be a bot/crawler | info | crawler bit (64) set |
| The variant of this exposure was determined by a rule override | info | rule 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.