Pixel Installation
Install the Adverfly tracking pixel on your website
The Adverfly Pixel is a JavaScript snippet that tracks user interactions on your website. It captures pageviews, events, and conversions to power your analytics.
Quick Start
Add the following code to the <head> section of your website:
<script>
window.adverfly = window.adverfly || [];
function advPxl() {
var args = [false];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
adverfly.push(args);
}
advPxl("init", YOUR_WORKSPACE_ID);
window.adverfly.store_currency = "EUR";
window.adverfly.store_timezone = "Europe/Berlin";
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://sos-de-fra-1.exo.io/adv/advv2.01.js";
document.getElementsByTagName("head")[0].appendChild(script);
</script>
Replace YOUR_WORKSPACE_ID with your Workspace ID from the Adverfly dashboard.
Configuration
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace_id | number | Required | Your Adverfly Workspace ID |
store_currency | string | Required | Your store's base currency (e.g., EUR, USD) |
store_timezone | string | Required | Your store's timezone (e.g., Europe/Berlin) |
Currency Conversion
The store_currency setting is important for accurate revenue tracking. If a transaction comes in with a different currency (e.g., a customer pays in USD), Adverfly automatically converts it to your store currency using the current exchange rate.
Example: Your store currency is EUR. A customer pays $129 USD. Adverfly converts this to ~€119 EUR in your reports.
Timezone
The store_timezone ensures all events and conversions are recorded in your local time, making your reports easier to read and analyze.
Tracking Events
Track custom events with the Adverfly Pixel
Once the pixel is installed, you can track custom events to capture user interactions.
Basic Event Tracking
Use the advPxl function to track events:
// Add to Cart
advPxl("event", "add_to_cart");
// Initiated Checkout
advPxl("event", "initiated_checkout");
Standard Events
| Code | Description |
|---|---|
pageview | User views a page (tracked automatically on init) |
add_to_cart | User adds item to cart |
initiated_checkout | User starts checkout process |
Autocapture
The v3 pixel can capture user interactions automatically — clicks, form submits, input changes, rage clicks, and copy/cut actions — without manual advPxl calls. Disabled by default: opt in per workspace by setting window.adverfly.activate_autocapture = true before init.
window.adverfly = window.adverfly || [];
window.adverfly.activate_autocapture = true;
advPxl("init", 8397799);
Captured Events
| Code | Description |
|---|---|
$click | Fired on clicks. Target is resolved to the nearest interactive ancestor (a, button, input, select, textarea, label, form, or role=button|link|menuitem). |
$submit | Fired on form submits. Includes form action and method. |
$change | Fired on <select> changes and checkbox/radio toggles. Text input values are NEVER captured. |
$rageclick | Fired when the same element is clicked 3 or more times within 1 second. |
$copy | Fired when a user copies content. Only metadata is captured — the copied text itself is never captured. |
$cut | Fired when a user cuts content. Only metadata is captured. |
Privacy
Autocapture is designed to never leak sensitive data. The following are enforced client-side before anything is sent:
- Input types never captured:
password,hidden,file. - Sensitive fields blocked by name / autocomplete: credit card (
cc-*,card-num,card-no), CVC/CVV, expiry, SSN, social, password, API keys, auth tokens, one-time-codes. - Freeform input values never read:
<input type="text|email|tel|...">and<textarea>values are never included. Only<select>options and checkbox/radio state are captured. - Text scrubbing: any token in visible text that looks like a credit card number, SSN, or a run of 13+ digits is stripped before sending. Result is truncated to 200 characters.
- Opt-out selectors: elements (and their descendants) marked with class
adv-no-captureor attributedata-adv-no-captureare skipped entirely.
Excluding Elements
Exclude a specific element (and all its children) from autocapture:
<!-- Class-based -->
<div class="adv-no-capture">
<input type="text" name="internal-note" />
</div>
<!-- Attribute-based -->
<section data-adv-no-capture>
<button>Internal action</button>
</section>
Configuration
Fine-grained control via window.adverfly.autocapture_config (set before init):
| Code | Description |
|---|---|
url_allowlist | Array of strings (substring match) or RegExp. If set, autocapture runs only on URLs matching at least one entry. |
url_ignorelist | Array of strings or RegExp. Autocapture is skipped on URLs matching any entry. |
element_allowlist | Array of lowercase tag names. Only elements with these tags are captured. |
css_selector_allowlist | Array of CSS selectors. Only elements matching at least one selector are captured. |
window.adverfly = window.adverfly || [];
window.adverfly.autocapture_config = {
url_ignorelist: [/\/admin/, "/debug"],
css_selector_allowlist: [".track-me", "[data-adv-track]"],
};
advPxl("init", 8397799);
Rate Limiting
Built-in limits protect your site and your event quota:
- Global: max 30 autocapture events per second.
- Per element: minimum 500 ms between events on the same element.
- Rage click: 1 s cooldown per element after a
$rageclickfires.
Tracking Conversions
Track purchase and lead conversions
Conversions are the most important events to track. They represent completed goals like purchases or lead submissions.
Purchase Conversion
Track when a user completes a purchase:
advPxl("conversion", "purchase", {
transaction_id: "order123", // required
transaction_gross_revenue: 10999, // required (in cents)
transaction_currency: "EUR", // required
transaction_shipping_costs: 499, // optional (in cents)
transaction_tax: 1900, // optional (in cents)
transaction_city: "Berlin", // optional
transaction_country_code: "DE", // optional
transaction_discount_code: "SUMMER20", // optional
customer_id: "customer@email.com", // optional
is_new_customer: 1, // optional (1 = new, 0 = returning)
});
Conversion Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_id | string | Required | Unique order ID |
transaction_gross_revenue | number | Required | Total revenue in cents (e.g., 10999 = €109.99) |
transaction_currency | string | Required | Currency code (EUR, USD, etc.) |
transaction_shipping_costs | number | Optional | Shipping costs in cents |
transaction_tax | number | Optional | Tax amount in cents |
transaction_city | string | Optional | Customer's city |
transaction_country_code | string | Optional | ISO country code (DE, US, etc.) |
transaction_discount_code | string | Optional | Applied discount/coupon code |
customer_id | string | Optional | Customer identifier (e.g., email) |
is_new_customer | number | Optional | 1 = new customer, 0 = returning |
With Line Items
You can also pass individual line items:
advPxl("conversion", "purchase", {
transaction_id: "order123",
transaction_gross_revenue: 2000,
transaction_currency: "EUR",
transaction_shipping_costs: 350,
transaction_tax: 150,
transaction_city: "Berlin",
transaction_country_code: "DE",
customer_id: "customer@email.com",
is_new_customer: 1,
transaction_items: [
{
transaction_item_id: "123",
transaction_item_name: "Product 1",
transaction_item_price: 1000,
transaction_item_tax: 50,
transaction_item_quantity: 2,
},
{
transaction_item_id: "124",
transaction_item_name: "Product 2",
transaction_item_price: 500,
transaction_item_quantity: 1,
},
],
});
Item Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_item_id | string | Required | Product/SKU ID |
transaction_item_name | string | Required | Product name |
transaction_item_price | number | Required | Item price in cents |
transaction_item_quantity | number | Required | Quantity purchased |
transaction_item_tax | number | Optional | Item tax in cents |
Lead Conversion
Track when a user submits a lead form:
advPxl("conversion", "lead", {
transaction_id: "lead-456",
});
Google Tag Manager
Install Adverfly via Google Tag Manager
You can install the Adverfly Pixel using Google Tag Manager for easier management.
Installation Steps
- Open your GTM container
- Create a new Tag
- Choose Custom HTML
- Paste the following code:
<script>
window.adverfly = window.adverfly || [];
function advPxl() {
var args = [false];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
adverfly.push(args);
}
advPxl("init", YOUR_WORKSPACE_ID);
window.adverfly.store_currency = "EUR";
window.adverfly.store_timezone = "Europe/Berlin";
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://sos-de-fra-1.exo.io/adv/advv2.01.js";
document.getElementsByTagName("head")[0].appendChild(script);
</script>
- Set the trigger to All Pages
- Save and publish
Configuration
| Code | Description |
|---|---|
store_currency | Your store's base currency. Transactions in other currencies are auto-converted. |
store_timezone | Your store's timezone for accurate event timestamps in reports. |
Tracking Conversions via Data Layer
If you're using the Data Layer for e-commerce events:
<script>
// This tag should fire on the purchase confirmation page
advPxl("conversion", "purchase", {
transaction_id: {{DL - Transaction ID}},
transaction_gross_revenue: {{DL - Revenue}} * 100,
transaction_currency: {{DL - Currency}}
});
</script>
Tracking Custom Events
Create additional tags for custom events:
<script>
// Add to Cart tag - trigger on add_to_cart event
advPxl("event", "add_to_cart");
</script>
<script>
// Initiated Checkout tag - trigger on checkout start
advPxl("event", "initiated_checkout");
</script>
Recommended Triggers
| Code | Description |
|---|---|
Base Pixel | All Pages |
Purchase Conversion | purchase event / thank you page |
Add to Cart | add_to_cart event |
Initiated Checkout | initiated_checkout event |
Shopify Integration
Install Adverfly on your Shopify store
Adverfly integrates natively with Shopify's Customer Events API for accurate tracking.
Installation Steps
- Go to your Shopify Admin
- Navigate to Settings → Customer events
- Click Add custom pixel
- Name it "Adverfly"
- Paste the following code:
const script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://sos-de-fra-1.exo.io/adv/script-shopify.js";
document.getElementsByTagName("script")[0].parentNode.appendChild(script);
window.adverfly_web_pixel = true;
window.adverfly_init = init;
window.adverfly_browser = browser;
window.adverfly_settings = api.settings;
window.adverfly = window.adverfly || [];
window.advPxl = function () {
adverfly.push([false, ...arguments]);
};
analytics.subscribe("all_events", (event) => {
window.adverfly.push(["all_shopify_events", event]);
advPxl("init", YOUR_WORKSPACE_ID);
window.adverfly.store_currency = "EUR";
window.adverfly.store_timezone = "Europe/Berlin";
if (event.name === "page_viewed") {
advPxl("check", "vikeys", event);
}
});
Replace YOUR_WORKSPACE_ID with your Workspace ID and set your store's currency and timezone.
Configuration
| Code | Description |
|---|---|
store_currency | Your store's base currency. Transactions in other currencies are auto-converted. |
store_timezone | Your store's timezone for accurate event timestamps in reports. |
Tracked Events
The Shopify integration automatically tracks all standard e-commerce events:
| Code | Description |
|---|---|
page_viewed → pageview | Shopify page_viewed event, recorded as pageview in Adverfly |
product_added_to_cart → add_to_cart | Shopify add to cart event, recorded as add_to_cart in Adverfly |
checkout_started → initiated_checkout | Shopify checkout start, recorded as initiated_checkout in Adverfly |
checkout_completed → purchase | Shopify purchase event, recorded as purchase conversion in Adverfly |
Surveys
Embed Adverfly surveys on your website
Embed Adverfly surveys directly on your website to collect customer feedback, post-purchase reviews, or NPS scores.
Quick Start
If you already have the Adverfly pixel installed, simply add this div where you want the survey to appear:
<div data-adv-survey-id="SURVEY_ID" data-adv-workspace-id="WORKSPACE_ID"></div>
Replace SURVEY_ID with your survey ID and WORKSPACE_ID with your workspace ID (both found in the survey settings).
The pixel automatically detects survey embeds and renders them.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
data-adv-survey-id | string | Required | Your survey ID |
data-adv-workspace-id | string | Required | Your workspace ID |
data-adv-survey-version | string | Optional | Specific survey version (uses latest by default) |
data-adv-order-id | string | Optional | Order/transaction ID to link survey responses to a specific purchase |
data-adv-lang | string | Optional | Language code like de, fr, en, or default. Omitting it keeps the legacy English UI fallback. |
Customer & Transaction Tracking
Survey events automatically include customer_id and transaction_id when set via the Adverfly Layer. This allows you to link survey responses to specific customers and orders.
<script>
window.adverfly = window.adverfly || [];
function advPxl() { adverfly.push([!1, ...arguments]); }
advPxl("init", WORKSPACE_ID);
window.adverfly.store_currency = "EUR";
window.adverfly.store_timezone = "Europe/Berlin";
/* Set customer and transaction IDs */
window.adverfly.customer_id = "customer_123";
window.adverfly.transaction_id = "order_456";
</script>
Adverfly Layer Properties
| Parameter | Type | Required | Description |
|---|---|---|---|
window.adverfly.customer_id | string | Optional | Customer ID to associate with survey responses |
window.adverfly.transaction_id | string | Optional | Transaction/Order ID to link survey responses to purchases |
window.adverfly.store_currency | string | Optional | Store currency (e.g., EUR, USD) |
window.adverfly.store_timezone | string | Optional | Store timezone (e.g., Europe/Berlin) |
These properties are automatically included in all survey events (survey_opened, survey_answer, survey_completed).
Multi-Language Support
Surveys support multiple languages. Set data-adv-lang to display translated content. Use default to force the survey base content while keeping the UI fallback in English:
<div
data-adv-survey-id="SURVEY_ID"
data-adv-workspace-id="WORKSPACE_ID"
data-adv-lang="de"
></div>
| Code | Description |
|---|---|
default | Base survey content with English UI fallback |
en | English UI, and translations.en if configured |
de | German |
fr | French (if configured) |
If data-adv-lang is omitted entirely, the public embed keeps the legacy behavior: English UI with the survey base content. Translations must be configured in the survey settings within Adverfly.
Survey Features
- Required questions: Questions marked as required show a red asterisk (*) and validate before proceeding
- Multi-step navigation: Surveys display one question at a time with Back/Next buttons
- Progress indicator: Shows current step and progress bar
- Thank you screen: Customizable message after submission
Survey Requirements
For the embed to work, your survey must:
- Be set to live status
- Have the Adverfly v2 pixel installed on the page (surveys are not supported in the v1 pixel)
Survey Events
The pixel automatically tracks these events for analytics:
| Code | Description |
|---|---|
survey_opened | Fired when survey is displayed to user |
survey_answer | Fired for each question answered |
survey_completed | Fired when user submits the survey |
All events include form_id, submission_id, and (if set) customer_id and transaction_id.
Widgets
Live personalization widgets — popups, banners, countdowns, and toasts
Adverfly Widgets render personalized popups, banners, countdowns and toast notifications on your storefront. The right variant is picked per visitor in real time by the personalization engine based on the segment they belong to.
How it works
Visitor lands on page
↓
Pixel calls /public/personalize with customer_id + session context
↓
Engine evaluates active widgets for this trigger + visitor's segment
↓
Pixel renders the winning widget (popup / banner / countdown / toast)
↓
All interactions (impression, click, dismiss, success) tracked to ClickHouse
You design widgets in the Adverfly app under Widgets. Each widget targets one segment (or "Everyone") and is auto-rendered when its trigger fires.
Installation
The widget pixel ships as part of the standard Adverfly tracking pixel — if you already have preset-pixel-adv.js (v3) installed, widgets work automatically with no extra code.
Shopify
Install the Adverfly Widgets custom app and enable the Adverfly Widgets App Embed Block in the theme editor under Online Store → Themes → Customize → App Embeds. The block's Workspace ID setting wires the pixel to your account.
For inline placements, drag the Adverfly Inline Widget App Block into any section.
Other platforms (theme.liquid, GTM, manual)
If you already have the Adverfly pixel on your site, enable widgets per workspace by setting window.adverfly.activate_widgets = true before advPxl("init", ...). Without this flag, the pixel does not call /personalize automatically — manual advWidgets.fetchAndRender(...) calls still work.
window.adverfly = window.adverfly || [];
window.adverfly.activate_widgets = true;
advPxl("init", 8397799);
Auto-Triggers
Once activate_widgets = true is set, widgets fire automatically based on their trigger_type:
| Code | Description |
|---|---|
page_view | On every page load. |
exit_intent | When the cursor leaves the top of the page. |
post_purchase | After a `conversion` event fires (set window.adverfly.transaction_id first). |
scroll_depth | After visitor scrolls past N% of the page. |
time_on_page | After N seconds on the page. |
Manual Rendering
If you want to trigger a widget yourself (e.g. after a custom event in your app), call advWidgets.fetchAndRender:
/* Trigger a custom widget configured for trigger="custom_event" */
window.advWidgets.fetchAndRender("custom_event");
/* Pass extra context that's added to the engine evaluation */
window.advWidgets.fetchAndRender("post_purchase", {
transaction_id: "order_123",
cart_value: 49.9,
});
Inline Widgets
Inline widgets render at a specific spot in your page rather than as overlays. Drop a <div> with data-adv-inline into your theme:
<div
data-adv-inline
data-adv-trigger="pdp_above_cta"
data-adv-workspace="WORKSPACE_ID"
style="min-height: 80px"
></div>
Configure a widget in Adverfly with trigger_type = "inline" and a matching trigger value (e.g. pdp_above_cta). The pixel mounts the personalized variant inside that div on page load.
| Parameter | Type | Required | Description |
|---|---|---|---|
data-adv-inline | flag | Required | Marks the element as an inline widget mount. |
data-adv-trigger | string | Required | Trigger name to fetch from Adverfly. Unique per placement. |
data-adv-workspace | string | Required | Your Adverfly workspace ID. |
Tracked Events
Every widget interaction is logged to ClickHouse pixel_events automatically — same pipeline as survey events, joinable by customer_id.
| Code | Description |
|---|---|
widget_impression | Widget rendered into the DOM. |
widget_click | User clicked the CTA. Properties include cta_url. |
widget_dismiss | User closed the widget manually (X button). |
widget_auto_dismissed | Toast timer expired or countdown ran out. |
widget_success | Goal completed — fired manually via window.advWidgets.success(...). |
Common payload fields
Every widget event carries these top-level fields (matches the survey schema for easy joining):
| Parameter | Type | Required | Description |
|---|---|---|---|
form_id | string | Required | The widget's variant_id (column reused for queryability). |
customer_id | string | Optional | Set via window.adverfly.customer_id — automatically hashed server-side. |
transaction_id | string | Optional | Order ID if available — enables conversion attribution. |
properties | JSON | Required | Stringified JSON with widget_type, trigger, and event-specific fields like cta_url. |
Tracking Goal Completion (Success API)
When a visitor completes the goal of a widget — submits an email, claims a discount, signs up — call the success API. This fires a widget_success event you can query later for conversion analytics.
window.advWidgets.success("widget-variant-id", {
email: "user@example.com", // auto-hashed → email_hashed (SHA-256, lowercased)
value: 19.9,
source: "newsletter_form",
});
The raw email is never transmitted — the pixel hashes it client-side before sending. Pass email_hashed directly if you've already hashed elsewhere.
| Parameter | Type | Required | Description |
|---|---|---|---|
variantId | string | Required | The widget's variant_id (visible in Adverfly under the widget detail). |
props.email | string | Optional | Auto-hashed to email_hashed. Raw value never leaves the browser. |
props.email_hashed | string | Optional | Skip hashing — pass an already-hashed value (SHA-256 of lowercased email). |
props.value | number | Optional | Optional value associated with the success (e.g. discount amount, order value). |
props.* | any | Optional | Any custom properties — included verbatim in the event payload. |
Manual Click Tracking
For inline widgets that render custom HTML, attribute clicks manually:
window.advWidgets.trackClick("widget-variant-id", {
cta_url: "/checkout",
source: "custom_cta",
});
Joining Widgets to Conversions
Widget events live in pixel_events and conversions in pixel_conversions. Join by customer_id (hashed) or by visitor_id for anonymous attribution.
-- Click-through rate per widget, last 30 days
SELECT
form_id AS widget_id,
countIf(name = 'widget_impression') AS impressions,
countIf(name = 'widget_click') AS clicks,
countIf(name = 'widget_success') AS successes,
clicks / nullIf(impressions, 0) AS ctr
FROM pixel_events
WHERE store_id = {workspace_id:String}
AND name LIKE 'widget_%'
AND dt >= today() - 30
GROUP BY form_id;
-- Attribute conversions to a widget impression (24h window, visitor_id link)
SELECT
e.form_id AS widget_id,
sum(c.transaction_gross_revenue / 100) AS attributed_revenue
FROM pixel_events AS e
INNER JOIN pixel_conversions AS c
ON e.visitor_id = c.visitor_id
AND e.store_id = c.store_id
AND c.dt BETWEEN e.dt AND e.dt + INTERVAL 1 DAY
WHERE e.store_id = {workspace_id:String}
AND e.name = 'widget_impression'
GROUP BY e.form_id;
Manual-Only Mode
If activate_widgets = true is not set, no auto-triggers fire — but window.advWidgets.fetchAndRender(trigger, extra) still works for fully manual rendering. Use this if you want full control over when /personalize is called.
See Also
- Tracking Events — base event API the widget events ride on
- Surveys — sister product, same event pipeline, similar embed pattern
- Pixel Installation — how the pixel boots and the
window.adverflyqueue
Personalization SDK
Headless JS SDK to render Adverfly widgets with your own components
The @adverfly/sdk JavaScript package lets you fetch the right personalized variant for the current visitor and render it however you want — your own React/Vue/Svelte components, server-rendered HTML, or even mobile apps.
The standard tracking pixel still works as a no-code option. Use the SDK when you need:
- Pixel-perfect brand control — no iframe, no CSS overrides
- Server-side / Edge personalization — Cloudflare Workers, Vercel Edge, Next.js Server Components
- Custom UI — inline blocks, mobile screens, anything beyond popup/banner/toast/countdown
- Strict typing — your
configshape becomes a TypeScript generic
Install
Three formats, same code — pick whichever fits your stack.
Vanilla <script> (no build step)
<script src="https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/adverfly.iife.js"></script>
<script>
const adv = new Adverfly({ workspaceId: 188334 });
/* `Adverfly` is now a global class — no module system required. */
</script>
Pin a version in production: @adverfly/sdk@0.1.0/dist/adverfly.iife.js.
ES modules in the browser
<script type="module">
import { Adverfly } from "https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/index.mjs";
const adv = new Adverfly({ workspaceId: 188334 });
</script>
npm (build pipelines, Node, SSR)
npm install @adverfly/sdk
import { Adverfly } from "@adverfly/sdk";
Works in browsers and Node 18+. Zero peer dependencies. Bundle is ~6 KB minified for the IIFE build, ~12 KB for the ESM build.
Quick start
import { Adverfly } from "@adverfly/sdk";
const adv = new Adverfly({ workspaceId: 188334 });
await adv.identify({ email: "user@example.com" });
adv.setContext({
cart_value: 49.9,
last_viewed_creatives: ["sku_a", "sku_b"],
});
const variant = await adv.personalize({ trigger: "exit_intent" });
if (variant) {
showMyPopup({
title: variant.config.title,
copy: variant.config.copy,
onCtaClick: () => adv.click(variant.id),
onDismiss: () => adv.dismiss(variant.id),
});
await adv.trackImpression(variant.id);
}
Constructor
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | number | Required | Your Adverfly workspace ID. |
apiUrl | string | Optional | Override for self-hosted or staging. Defaults to https://b.adverfly.com. |
customerId | string | Optional | Pre-identify without calling identify(). |
debug | boolean | Optional | Mirror decisions + events to console.log under [adverfly]. |
manualIdentity | boolean | Optional | Skip auto-anonymous-id (for SSR / when you manage identity). |
Identity
await adv.identify({
email: "user@example.com", // hashed (SHA-256, lowercased) → never sent raw
transactionId: "order_123", // optional, for post-purchase triggers
});
To reset identity (e.g. on logout):
adv.reset();
Personalize
const variant = await adv.personalize<MyConfigShape>({
trigger: "exit_intent",
surface: "widget", // optional, defaults to "widget"
context: { device_battery_low: true }, // merged on top of session context
});
// { id, config, reason } | null
Strongly-typed config:
interface PopupConfig {
title: string;
copy: string;
cta?: string;
cta_url?: string;
image_url?: string;
}
const variant = await adv.personalize<PopupConfig>({ trigger: "exit_intent" });
if (variant) {
console.log(variant.config.title); // typed!
}
Tracking
All tracking events are written to the same ClickHouse pixel_events table the standard pixel uses — joinable by customer_id (hashed) for conversion attribution.
| Code | Description |
|---|---|
trackImpression(variantId) | Widget rendered into your UI. |
click(variantId, props?) | User clicked the CTA. props can include cta_url. |
dismiss(variantId) | User closed the widget manually. |
autoDismissed(variantId, reason?) | Timer expired (countdown, toast). |
success(variantId, props?) | Goal completed. If props.email is set, auto-hashed before send. |
Events
Subscribe to the lifecycle for analytics, debugging, or custom rendering hooks.
const off = adv.on("decision", ({ trigger, variant }) => {
console.log(`[${trigger}] →`, variant?.id ?? "no match");
});
/* Returns an unsubscribe function */
off();
Available events: decision, impression, click, dismiss, auto_dismissed, success, error.
React
A first-class @adverfly/react package is planned. For now you can wrap the core SDK in a hook:
import { Adverfly, type Variant } from "@adverfly/sdk";
const AdverflyContext = createContext<Adverfly | null>(null);
export function usePersonalization<T>(trigger: string) {
const adv = useContext(AdverflyContext)!;
const [variant, setVariant] = useState<Variant<T> | null>(null);
useEffect(() => {
adv.personalize<T>({ trigger }).then((v) => {
setVariant(v);
if (v) adv.trackImpression(v.id);
});
}, [adv, trigger]);
return {
variant,
click: (props?) => variant && adv.click(variant.id, props),
dismiss: () => variant && adv.dismiss(variant.id),
success: (props?) => variant && adv.success(variant.id, props),
};
}
Full example: sdks/javascript/examples/react-hook.tsx.
Server-side / Edge
Works anywhere fetch + crypto.subtle exist (Node 18+, Bun, Cloudflare Workers, Vercel Edge).
/* Cloudflare Worker — server-render a personalized hero block */
export default {
async fetch(request: Request) {
const adv = new Adverfly({
workspaceId: 188334,
manualIdentity: true, // we manage identity ourselves
});
await adv.identify({ customerId: getCookieUserId(request) });
const variant = await adv.personalize({
trigger: "ssr_hero",
context: { country: request.cf?.country },
});
return new Response(renderHero(variant?.config), {
headers: { "content-type": "text/html" },
});
},
};
Privacy
- Emails are hashed client-side (SHA-256, lowercased + trimmed) before any network call.
- No cookies set by the SDK. Anonymous IDs go to
localStorage; opt out withmanualIdentity: true. - CORS-clean. POST + JSON, no preflight surprises.
See also
- Widgets — the no-code drop-in pixel option
- Tracking Events — base event schema (the SDK rides on the same beacon)