Exit-Intent Widgets
Pro FeatureCatch visitors before they leave. Exit-Intent Widgets are customer-defined popups that fire the moment a visitor signals they're about to abandon the page — with built-in analytics on every impression, click, and dismissal.
Exit-Intent Widgets are available on Pro, Scale, and Enterprise plans. Upgrade your plan to enable this feature.
Overview
A widget is a small, configurable overlay rendered by the Zenovay tracking script when an exit-intent signal is detected. You design the content, target who sees it, and Zenovay handles delivery, frequency capping, and analytics.
When widgets fire
The tracker uses two device-specific exit signals:
| Device | Signal |
|---|---|
| Desktop | mouseleave event with the cursor crossing the top edge of the viewport |
| Mobile / tablet | A fast upward scroll (back-to-top gesture) or the page returning from the background tab via visibilitychange |
Both signals are debounced and respect the per-widget frequency cap so the same visitor isn't shown the same widget twice in a session.
Widgets shown by the tracker run client-side. In cookieless mode, the tracker does not persist visitor IDs between page loads, so the cross-tab dedupe is best-effort only — a single visitor opening the same page in two tabs may see the widget in each.
Setup
Widgets are managed entirely from the Zenovay dashboard — no code changes required beyond having the tracking script installed.
- Go to your dashboard and select the website you want to add a widget to.
- Open the Widgets tab.
- Click New widget.
- Walk through the four-step builder.
The 4-step builder
Step 1 — Content
Define what the widget says.
- Title — the headline (max 120 chars)
- Body — the supporting copy (max 500 chars, plain text)
- Primary CTA — button label and destination URL (or a
goal_eventname if you want a tracked goal completion instead of a redirect)
Step 2 — Targeting
Decide who sees it.
- Page pattern — which URLs the widget runs on (see Targeting semantics)
- Country — ISO 3166-1 alpha-2 country code list (e.g.
US,DE,FR); leave empty for all countries - Device — one or more of
desktop,mobile,tablet - Frequency cap — max times shown per visitor per N days (e.g. once every 7 days)
- Min visit count — only fire after the visitor's Nth pageview on this site (requires non-cookieless mode)
Step 3 — Design
Style the widget to fit your brand.
- Position, width, border radius (see Design enums)
- Background, text, and button colors (hex)
- Image with selectable position (top, left, right, or full-bleed background)
- Secondary button (optional) — either a passive
dismissaction or a secondarylink
Step 4 — Schedule
- Status —
draft,active, orpaused - Start date / End date (optional) for time-boxed campaigns
Hit Save and the widget goes live within a minute (the tracker fetches active widgets on the next page load).
Targeting semantics
Page pattern
By default, the page-pattern field is a substring match against the visitor's pathname.
| Pattern | Matches |
|---|---|
/blog | /blog, /blog/post-1, /de/blog/x, /team-blog/about |
/checkout | /checkout, /checkout/payment, /api/checkout/v2 |
* | every page |
For more precise matching, use explicit globs:
| Pattern | Matches |
|---|---|
/blog/* | /blog/post-1, /blog/category/x (NOT /de/blog/...) |
/checkout/*/payment | /checkout/abc/payment, /checkout/xyz/payment |
/products/[id] | /products/123, /products/abc-def |
Substring is the default because most customers expect "/blog" to match all blog pages regardless of locale prefix. Use globs (*) when you need to anchor the start or end of the path.
Country
Comma-separated ISO 3166-1 alpha-2 codes:
US,CA,GB,DE,FR
Country detection uses the visitor's IP geolocation at the time of the pageview. EU visitors with GPC enabled are not enriched, so country-based targeting will fall through to "all countries" for those visitors.
Device
Pick one or more of the three buckets:
desktop
mobile
tablet
The tracker classifies devices by user-agent and viewport width, identical to the rest of the dashboard.
Frequency cap
Defined as max_shows_per_n_days. The tracker stores a per-widget impression timestamp in the visitor record (server-side, never in cookies/localStorage). When frequency_cap_n_days has not yet elapsed since the last shown event for this widget × visitor pair, the widget is suppressed.
Min visit count
Setting min_visit_count > 1 requires the tracker to remember how many times this visitor has loaded the site. Because Zenovay's tracker runs cookieless by default when consent has not been given, this targeting rule will silently fall through to "fire on first visit" for any visitor in cookieless mode.
If you depend on this rule, ensure the tracker is loaded with data-cookieless="false" (i.e. after consent), otherwise the rule is a no-op for the vast majority of EU traffic.
Design enums
The design fields are security-locked enums — the API accepts only the values below. This protects every Zenovay-served widget from CSS injection.
Position
center
top-banner
bottom-banner
top-right
top-left
bottom-right
bottom-left
top-banner and bottom-banner are full-width strips. The four corner positions are smaller, fixed cards.
Width
sm (320px max)
md (480px max)
lg (640px max)
xl (800px max)
Widths are responsive — they shrink to fit smaller viewports and never exceed the visitor's screen.
Border radius
none
sm
md
lg
full
full rounds the widget into a pill (only meaningful with a banner position).
Image position
top
left
right
background
background places the image as a full-bleed backdrop with a contrast scrim over text.
Secondary action
dismiss
link
dismiss closes the widget without recording a click. link records a secondary_clicked event and navigates to the configured URL.
Analytics
Every widget records four discrete event types:
| Event | When it fires |
|---|---|
shown | The widget became visible in the viewport |
clicked | The visitor clicked the primary CTA |
secondary_clicked | The visitor clicked the secondary button (when configured as link) |
dismissed | The visitor closed the widget without clicking |
Detail view
Open any widget from the Widgets tab to see:
- CTR —
clicked / shown, with comparison vs the website average - Per-day chart — daily impression / click / dismiss bars with sparklines
- Conversion funnel —
shown → clicked → goal completed(when the CTA points to a tracked goal) - Top pages — the URLs where the widget fired most often, with per-page CTR
- Device breakdown — desktop / mobile / tablet split for shown vs clicked
All metrics respect the same date-range and segment filters as the rest of the dashboard.
Privacy
Widget analytics events use the same cookieless event pipeline as the rest of Zenovay:
- The visitor ID is a server-computed daily-salted SHA-256 hash of (IP subnet + user-agent + daily salt). No cookies, no
localStorageare written on the visitor's browser. - The cross-tab dedupe (so the same visitor doesn't see the widget twice in two tabs) is best-effort only in cookieless mode — without persistent storage, two tabs are indistinguishable from two visitors with the same IP/UA fingerprint.
- Visitors with Global Privacy Control (
Sec-GPC: 1) are not enriched — country and B2B fields are dropped server-side, but the widget will still fire if the page pattern, device, and frequency cap are satisfied. - No widget content, no DOM snapshot, and no visitor input is collected. Only the four event types listed above.
If you need stricter targeting (e.g. show only to logged-in users), gate the widget on a custom event from your own application code rather than relying on cross-tab persistence.
Plan limits
| Plan | Widgets per website |
|---|---|
| Free | 0 (locked) |
| Pro | 3 |
| Scale | 10 |
| Enterprise | Unlimited |
Limits are enforced on the API at create time and surfaced as an error in the dashboard if you try to exceed them. To raise your limit, upgrade your plan.
API access
Manage widgets programmatically:
# List widgets for a website
GET /api/popup-widgets/:websiteId
Authorization: Bearer YOUR_API_KEY
# Get a single widget
GET /api/popup-widgets/:websiteId/:widgetId
# Get analytics for a widget
GET /api/popup-widgets/:websiteId/:widgetId/stats
# Create
POST /api/popup-widgets/:websiteId
# Update
PATCH /api/popup-widgets/:websiteId/:widgetId
# Delete
DELETE /api/popup-widgets/:websiteId/:widgetId
The public, tracker-facing endpoint (GET /api/popup-widgets/active/:trackingCode) is called automatically by the tracking script and does not require authentication.
Best practices
- Lead with value, not panic. "Save 20% on your first order" outperforms "Wait! Don't go!" by a wide margin.
- Cap to once per visitor per week at minimum. A widget on every visit becomes ad-blocker bait.
- Test on mobile separately. The mobile signals (scroll-up + visibility return) fire differently from desktop's
mouseleave. A widget that converts on desktop may be invisible on mobile. - Pair the CTA with a goal. Pointing the primary CTA to a tracked URL or
goal_eventlets you see the fullshown → clicked → convertedfunnel instead of just clicks. - Pause before iterating. Don't delete a widget you're A/B testing — pause it so the historical analytics remain attached.
Next Steps
- Conversion Funnels - Track multi-step conversion paths the widget can feed into
- Goals - Define the events your widget CTA should complete
- Conversion Incidents - Get alerted when widget-driven conversions drop