Custom Integration
A vs B talks to nine tools directly from the Integrations tab: Google Analytics 4, Google Tag Manager, Mixpanel, Segment, Amplitude, Heap, Fullstory, Contentsquare, and Adobe Analytics. For anything else, the event bus hands you every experiment event so you can route it wherever you like.
You no longer need custom code to get experiment data into the dataLayer. Turn on the Google Tag Manager card and A vs B pushes the events for you, with the field names and trigger name documented on the Google Tag Manager page. The rest of this page is for tools that have no card.
Subscribing to events
avsb.on?.('event', function (event) { console.log('A vs B event:', event);});avsb.on returns a function that removes your subscription, and you can subscribe as many times as you like. Where you put this matters: subscribe from Project JS and you catch every event, including the first experiment view. An inline script works too, as long as it sits below the install stub, which holds early calls until the snippet loads. See the event bus reference for the full record and the setup order.
window.avsb.onEvent no longer exists. It held a single callback, so a second integration silently replaced the first. Replace window.avsb.onEvent = fn with avsb.on('event', fn). The data your function receives is the same, with more fields on it.
What you receive
const exampleEvent: AvsbIntegrationRecord = { experiment_id: '482', experiment_name: 'Homepage hero', variation_id: '1913', variation_name: 'Variant B', event_type: 'exposure', // branch on this, not on the name event_name: 'avsb_experiment_viewed', event_id: '3f9a2b1c-7d4e-4a58-9c02-5e6d1f8b3a47', revenue: null, value: null, visitor_id: '...', page_url: 'https://example.com/', timestamp: 1753800000000, sdk_version: '1.0.0'};Purchases also carry currency and transaction_id, conversions carry goal_key, and recommendation events carry attributes. Records from preview or internal traffic carry source. Every field is listed on the event bus reference.
Examples
Send experiment views to a tool with no card:
avsb.on?.('event', function (event) { if (event.event_type !== 'exposure') return; myAnalytics.track('Experiment Viewed', { experiment: event.experiment_name, variation: event.variation_name });});Post everything to your own endpoint:
avsb.on?.('event', function (event) { fetch('/api/experiment-events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }).catch(function () { // fail quietly: never interrupt the visitor });});Pendo:
// `pendo` is the global Pendo's own agent installs on the page.declare const pendo: { track(name: string, properties: Record<string, unknown>): void } | undefined;avsb.on?.('event', function (event) { if (event.event_type !== 'exposure') return; if (typeof pendo === 'undefined') return; pendo.track('avsb_experiment', { experiment_name: event.experiment_name, variation_name: event.variation_name });});Ignore your own preview and internal traffic:
avsb.on?.('event', function (event) { if (event.source) return; // preview or internal QA session myAnalytics.track(event.event_name, { ...event });});Keep it quick and avoid blocking work. Use fetch() or XMLHttpRequest asynchronously for network calls. If your code throws, A vs B carries on and any other subscribers still run.
Reach for the bus when the tool you want has no card and is not reachable through Google Tag Manager. Common examples are Pendo, Intercom, HubSpot, an internal analytics system, or your own warehouse endpoint.