Overview
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
Paste the Adverfly script tag into your theme's layout/theme.liquid (just before </head>) — same pattern as Google Analytics or any other tag. The pixel ships the widget renderer in the storefront DOM, where the Shopify Web Pixel sandbox can't reach.
<script async src="https://cdn.adverfly.com/preset-pixel-adv.js"></script>
<script>
window.adverfly = window.adverfly || [];
window.adverfly.activate_widgets = true;
advPxl("init", {{ shop.metafields.adverfly.workspace_id | default: 'YOUR_WORKSPACE_ID' }});
</script>
The pixel auto-detects Shopify and skips auto-firing pageview / vikey events to avoid double-counting — those are tracked by the Shopify Web Pixel install. Widgets render normally.
Inline placements: drop a <div data-adv-inline data-adv-trigger="…" data-adv-workspace="…"> into any section file (see Inline Widgets below).
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