Embed the assistant on your storefront
Paste one script tag to load the assistant. Open it from a floating chat bubble, your own button, or both. No login, works from the site key.
The web tag puts the assistant on your own storefront. You paste one <script> snippet once; it loads a small loader that opens the assistant in a full-height side panel when a shopper triggers it. The panel docks to one edge of the screen and slides in horizontally from that edge. The conversation runs inside a cross-origin iframe served by iAdvize, so no chat content ever touches your page's DOM.
You decide what opens the panel: a floating chat bubble or a centered question bar the loader renders for you (below), a button, a link, or a menu item you already have on the page, or any combination of these. Both built-in surfaces are off by default.
If a technical or SEO review has to clear the tag first, Web tag performance has the weight, the request count, and the layout-shift guarantees.
Install the snippet
Open the site in the dashboard and go to Settings → Public access. The Install the tag on your site card holds the snippet, already filled in with this site's public key.
Copy the snippet and paste it once into your storefront, ideally just before </body> on every page:
<script>
;(function (w, d) {
w.iadvize =
w.iadvize ||
function () {
;(w.iadvize.q = w.iadvize.q || []).push(arguments)
}
w.iadvize.k = 'pk_site_YOUR_PUBLIC_KEY'
function l() {
var s = d.createElement('script')
s.async = true
s.src = 'https://tag.iadvize.ninja/tag/loader.js'
d.head.appendChild(s)
}
if (d.readyState === 'complete') l()
else w.addEventListener('load', l)
})(window, document)
</script>The snippet defines the window.iadvize command queue, stores your public key, and injects the loader after the page has loaded, so it never competes with your page's first render. Everything heavier (the panel, the iframe) loads later, only when a shopper opens the assistant.
Copy the dashboard snippet, not this one
The block above shows the shape of the snippet. Copy the real one from the
Install the tag card: it carries your site's actual public key. The key
(pk_site_…) is a public, non-secret identifier; it's safe to ship in page
source.
You don't need to turn on public access
The web tag works from a key that's minted when the site is created, so the Enable public access toggle on the same page does not gate it. That toggle only controls the hosted /s/<key> share link. You can embed the tag whether or not public access is on.
Allow the origins that will embed it
The panel only starts a conversation on a page whose origin is on this site's allowed origins list. This is a separate control from your storefront's own Content Security Policy: the panel checks the origin itself, before it will show anything.
Your storefront's own address is covered automatically: a site's URL is required when you create or edit it, and its origin is added to the allowed list for you, marked From site URL and not removable; it stays allowed for as long as it's the site's URL. If you change the URL later, the old origin stays on the list as a regular entry; it's not removed automatically.
To allow additional origins (a staging domain, a second brand site, a subdomain pattern), open Settings → Public access and use the Allowed origins card:
Type the origin in the field: either an exact one (https://shop.example.com) or a wildcard subdomain pattern (https://*.example.com, which matches any subdomain of example.com at any depth, such as shop.example.com or eu.shop.example.com, but never example.com itself).
Click Add. The origin shows up in the list right away.
Remove an origin you no longer need with the trash icon next to it. The one marked From site URL can't be removed this way. Update the site's URL instead if it needs to change.
If a shopper opens your page on an origin that isn't on the list, the panel doesn't stay blank: it shows an inert box naming the refused origin and pointing back to this setting, so you (or your web team) know exactly what to add.
No allowed origin, no install snippet
The Install the tag on your site card stays hidden until the site has at least one allowed origin, so you can't paste a snippet that would only ever show that refusal message. A brand-new site already has one, from its required URL.
Open the assistant
Nothing opens the panel on its own. Wire it to one of your own elements, either way:
A data-iadvize-open attribute on any element. The tag listens for clicks anywhere on the page and opens the panel when the click lands on (or inside) an element carrying the attribute:
<button data-iadvize-open>Ask our assistant</button>A JavaScript call, from your own click handler or component:
iadvize('open')Opening needs a real click
Both paths require a genuine user gesture. iadvize('open') only opens the
panel when it runs inside an active user activation (a click, a tap), so a
script can't silently open conversations on page load. A data-iadvize-open
click is a gesture by nature, so it always works.
While the panel is loading, it shows an opaque, branded background and a small loading indicator instead of a see-through gap. The conversation fades in once it's ready.
Auto-send a first message
A data-iadvize-open element can also send a first message in the same click, opt-in only. A plain data-iadvize-open with neither attribute below keeps working exactly as it does today: opens the panel, sends nothing. Adding this to an existing button changes nothing until you also add one of these two attributes.
<button data-iadvize-open data-iadvize-message="Where's my order?">
Track my order
</button>
<button data-iadvize-open data-iadvize-auto-send>Track my order</button>data-iadvize-message="…"sends that exact text, whatever the element itself displays. Setting this attribute is itself the opt-in: no second attribute needed.data-iadvize-auto-send(a boolean attribute; its presence is what matters, it needs no value) sends the element's own visible text (textContent, trimmed). Use it when the button or link's label already reads like something a shopper would say.
If an element carries both, data-iadvize-message wins whenever it's non-blank.
Only opt in where the text makes sense as a message
Auto-send sends whatever text resolves: the element's own label, or your
override. Tagging a large block of unrelated content with
data-iadvize-auto-send sends all of its text (truncated, see below), not
just "open."
A few behaviors worth knowing before you wire this up:
- No resolvable text → open only, plus a console warning. If you opted in (either attribute) but the resolved text is empty (an empty
data-iadvize-message=""and no text content, for instance), the panel still opens, nothing is sent, and the loader logs aconsole.warnso the misconfiguration doesn't pass silently. - Long text is truncated. A resolved message longer than roughly 200 characters is cut at the last word boundary before that mark, never mid-word. If there's no word break before the mark (a single very long token, or a script that doesn't use whitespace between words), it falls back to a hard cut at the same length.
- Every click sends, including a click into a conversation that's already open: there's no "first click only" restriction.
- A click that lands mid-reply doesn't collide with it. If the assistant is still streaming a response when another opted-in click comes in, the new message is held and sent as soon as that response finishes, rather than sent concurrently or dropped.
Add a floating launcher bubble or a question bar
If you don't want to build your own trigger, turn on one of the loader's own built-in surfaces instead: a floating chat bubble docked to a corner of the screen, or a centered, bottom-docked question bar a shopper can type into directly: sending their first message opens the panel in the same action, no separate click to open it first.
There is a third format, the inline button, which renders inside your own content flow at a point you declare rather than against the viewport. Everything in this section describes the two floating ones; the button behaves differently on three counts: it stays visible while the panel is open, it needs a declared page type, and it needs a mount point in your markup. See The inline button and Place an inline button on your pages.
The dashboard calls it Chat bubble; the API still says launcher
This page's identifiers are unchanged: the launcher key on iadvize('set', …), and the data-iadvize-launcher-host selector in Style engagement
widgets with CSS. Only the
dashboard's label for that widget format changed, from "Launcher" to "Chat
bubble". Nothing to update in your page code.
Both are managed as engagement widgets, on their own dashboard page, not on Settings → Public access anymore, which no longer has anything pointing back to it. Open the site in the dashboard and go to Engagement → Widgets (/dashboard/sites/{siteId}/engagement-widgets), then Add an engagement widget and pick the format from the catalog; see Engagement widgets for the full walkthrough of its Active switch, its offsets, its Stacking order, and its page-type targeting. A newly created widget is disabled with no page types selected, so it renders nowhere until you turn it on and pick at least one page type; its dashboard page says as much until you have.
Turning a widget on doesn't change the install snippet: it's the same snippet whether a widget is on or off. An already-installed tag picks up a change to its widgets on its next page load, with nothing to re-paste on your storefront.
A few things worth knowing about how these behave:
- A chat bubble's docked side is its own setting, independent of the panel. Set the widget's Position (Left/Right) on its own configuration page; it no longer has to match the Panel side chooser above. A question bar has no side setting; it's always horizontally centered.
- Both are restylable from the dashboard. Colours, size, corner radius, and shadow are per-widget fields that inherit your site's branding until you override them; a chat bubble additionally picks an icon from a built-in catalogue and can carry an optional per-language text label. See Appearance. Neither renders a remote image, and neither pops up a greeting. The question bar shows a translatable placeholder text in its input (configured per widget; see Engagement widgets) and a send button.
- You can go further in your own CSS. Both widgets expose a small, supported set of CSS custom properties and shadow parts, so front-end work on your storefront can restyle them beyond the dashboard's fields, with no extra CSP directive. See Style engagement widgets with CSS.
- Both hide while the panel is open, and reappear when the shopper closes it. An inline button doesn't: hiding it would collapse its box and move your page's content on every open and close.
- Page-type targeting decides where either can show, not just checkout. A widget with no page types selected never renders at all. See Page-type targeting for exactly how the allow-list works, including keeping a widget off a checkout page you've declared with
iadvize('set', { customData: { pageType: 'checkout' } }). This only hides the widget; if a conversation is already open on that page, it isn't closed. - Both fail closed. A widget only ever appears once the loader has successfully fetched this site's config. If that request fails or is blocked (a flaky connection, an ad blocker, whatever the cause), the page shows no chat bubble or question bar for that visit, with no retry and no fallback to a previous value. Nothing else about the tag depends on this fetch, so the rest of the panel keeps working either way.
- You can enable more than one, of either type. Each enabled widget renders as its own independent surface with its own offsets and stacking order. See Multiple widgets on one site.
- The page-side
launcheroverride only ever targets launcher widgets. Theiadvize('set', { launcher: … })command below (predating the question bar) has no equivalent for it: there's no page-side way to suppress or reconfigure a question bar outside the dashboard.
Override it from your own page
A page-side call always beats the dashboard configuration, whichever arrives first:
iadvize('set', { launcher: true }) // or false
iadvize('set', { launcher: { enabled: false } })
iadvize('set', { launcher: { offsetBottom: 80, offsetSide: 16 } })Pass true/false to override just whether it shows, or an object to touch enabled/offsetBottom/offsetSide individually; any field you leave out keeps its current value. Those three are the whole list: appearance isn't settable from the page. Colours, size, corner radius, shadow, icon, and label come from the widget's Appearance fields, or from your own CSS. An offset passed here also wins at every screen size, including below the 768px breakpoint where a widget's narrow-screen offsets would otherwise apply. This is a single, blanket override: it applies the same way across every launcher widget on the site, not one widget at a time. Unlike position/backdrop, this can be called again at any time, including after the panel is already open, so you can suppress every launcher bubble on specific routes of a single-page app without touching the dashboard configuration.
Why an admin might see enabled widgets but no bubble
A page-side iadvize('set', { launcher: … }) call always wins over the
dashboard configuration, regardless of which one arrives first. If a widget
is enabled and targeted at the right page type on the Engagement Widgets
page but doesn't show on a given page,
check that page's own code for a launcher override before assuming the
configuration didn't save.
The AI disclosure only reaches shoppers on hover, not on touch
The bubble carries iAdvize's fixed AI-transparency line ("You're chatting with an AI assistant…") as its accessible name and as a native title tooltip. This text isn't something you write or edit. aria-label is what reaches assistive technology, and it's always there. The title tooltip is different: like every native tooltip, it only appears on mouse hover, and it never appears at all on a touch device: there's no touch equivalent of hovering, so a shopper on a phone or tablet never sees it, no matter how long they press.
Don't rely on the tooltip as your only disclosure
If most of your traffic is mobile, the launcher's title effectively reaches
nobody before they tap it. This is a permanent limitation of how browsers
handle title, not a bug we plan to fix. Treat the launcher's tooltip as a
bonus for desktop/mouse visitors, not your disclosure mechanism. The
disclosure every shopper actually sees, on any device, is the one already
built into the conversation itself, as the first line once the panel opens.
Close the panel
Shoppers can close the panel three ways, and you don't need to wire any of them yourself:
- The ✕ in the chat header. Once the conversation has loaded, its header carries a close control.
- The Escape key, from anywhere (the host page or inside the conversation), except while the shopper is typing in the composer (Escape there keeps its normal behavior instead of closing the panel).
- Clicking outside the panel, but only if you've turned on the backdrop below. Without a backdrop, clicking the rest of the page never closes the panel: the assistant is a companion to browsing, not a takeover.
You can still close it yourself with iadvize('close').
No close control during the first instant
The ✕ lives in the conversation's own header, so it only appears once the conversation has loaded. For the brief moment between opening and that first paint, there's no visible way to close the panel. Escape still works during that moment.
Scrolling the page behind the panel never closes it, on any device.
Choose which side the panel slides in from
The panel is a full-height drawer, up to 640px wide, docked to one edge of the screen. On a screen 640px wide or narrower it fills the whole width. It slides in horizontally from that edge, from the right by default. There are two ways to dock it on the left instead, and you only need one:
From the dashboard. The Install the tag card has a Panel side chooser (Right / Left). Pick a side and the snippet in the card rewrites itself: choose first, then copy. Picking "Left" adds a single iadvize('set', { position: 'left' }) line to the snippet; "Right" is the default, so it stays out.
At runtime, from your own code:
iadvize('set', { position: 'left' }) // or 'right'There's no HTML attribute for this: position is set only through the snippet or the set command (any value other than 'left' / 'right' is ignored).
Set the side before the first open
The panel reads its position when it's first created: the moment a shopper
first opens it. Call set early (right after the snippet, on page load), not
after the panel is already on screen; setting it later won't move an open
panel. The dashboard snippet already does this: its set line runs before
anything can open the panel.
Add a dimmed backdrop
By default, the panel is a companion: it sits alongside your page, and the rest of the page stays visible and scrollable while a shopper is chatting. If you'd rather the panel feel like a focused, modal step (a guided flow, checkout support), turn on the backdrop. There are two ways to do it, and you only need one:
From the dashboard. The Install the tag card has a Dimmed backdrop toggle, right below Panel side. Turn it on and the snippet in the card rewrites itself: toggle first, then copy. Turning it on queues backdrop: true in the snippet's set call; off is the default, so it stays out. If you've also picked "Left" for the panel side, both options queue in that same set call rather than two separate ones.
At runtime, from your own code:
iadvize('set', { backdrop: true }) // default: falseWith the backdrop on, a dimmed scrim covers the rest of the page behind the panel, and clicking it closes the panel, the one outside-click-to-close path this adds. It also locks background scrolling while the panel is open, same as on a narrow screen (see below), and restores it on close.
Set the backdrop before the first open
Like position, backdrop is read when the panel is first created. Set it
early, before a shopper can open the panel.
Background scroll, in short:
| Screen | Backdrop | Page behind the panel |
|---|---|---|
| Desktop / wide (partial-width panel) | off | scrolls normally |
| Desktop / wide (partial-width panel) | on | locked |
| Mobile / narrow (panel fills the screen) | off | locked |
| Mobile / narrow (panel fills the screen) | on | locked |
On mobile the panel already covers the whole screen, so the page behind it is locked regardless of the backdrop setting.
Choose the shopper's language
The panel picks the language it displays (the launcher's disclosure, a chat bubble or question bar's label, the composer's placeholder, starter-question chips, and the conversation itself) from the page it's embedded on, before it ever looks at your storefront's own translation setup. Three signals are checked, in order, and the first one that matches one of the site's enabled languages wins:
- An explicit
iadvize('set', { locale: 'fr' })call, if one was queued before the loader booted. - The host page's own
<html lang="…">attribute. - The shopper's browser language (
navigator.languages).
If none of the three match an enabled language, the site's own default language is used. This runs automatically: most storefronts declare a correct <html lang> per locale already, so there's nothing to wire up.
iadvize('set', { locale: 'fr' })Force a language when the page's own signals can't be trusted: a theme that hard-codes lang="en" on every locale, or a multi-market page you want to show in a specific language regardless of what the shopper's browser reports. Set it early: locale is read once, from calls queued before the loader script runs, either in the install snippet itself or in a script tag that executes ahead of it. A set({ locale }) call made after the loader has already booted is a no-op; it won't retroactively change anything on the current page.
Resolved once per page load
The language is fixed for the whole page load. A later set({locale}) call,
or the page's lang attribute changing without a reload, has no effect until
the shopper's next navigation. On a single-page app, that means a route change
alone doesn't switch the widget's language.
A resumed conversation keeps its own language
This only decides the language for a new conversation. If a shopper already has one running and navigates to a page in a different language (from your site's French pages to its English ones, say), reopening the panel resumes that conversation in the language it was created with. The page's language never overrides an in-progress conversation.
The hosted link has the same default (the shopper's browser), plus its own override for a link you're sharing directly rather than embedding: append ?l= with a language code, for example https://www.iadvize.ninja/s/<public-key>?l=fr. Useful for a link in a French newsletter or on a French QR code, where there's no host page to read a lang attribute from.
Tell the assistant what page the shopper is on
By default the assistant only knows what the shopper types. Declare the current page and it can ground its answers in it instead: "is this in stock?" on a product page, "what's on sale here?" on a category page.
Call iadvize('set', { customData }) with one of four shapes, depending on the page:
// Home page
iadvize('set', { customData: { pageType: 'home' } })
// Category page
iadvize('set', {
customData: { pageType: 'category', categoryName: 'Chemise homme' },
})
// Product page
iadvize('set', { customData: { pageType: 'product', productId: 'XXX' } })
// Checkout page
iadvize('set', { customData: { pageType: 'checkout' } })pageType: 'home': no other field required.pageType: 'category':categoryNameis required: a display string (e.g."Chemise homme"), not a catalog id.pageType: 'product':productIdis required: your own catalog/connector id for the product, whatever your product feed already uses. Never an iAdvize id.pageType: 'checkout': no other field required. It doesn't change what the assistant knows; its effects today are hiding a chat bubble or question bar on that page, if you've excluded checkout from its page types, and (like any declared page type) narrowing which starter questions can show (see below).
An inline button won't render at all without this call
A chat bubble or question bar with page types selected still shows on a page
that declares none. An inline
button does not: it renders only
where pageType is declared and that type is one it targets. If your
storefront never calls customData, no inline button appears anywhere,
however it's configured.
pageUrl: optional on all four. Carried through as-is; never parsed to guess the page type, category, or product.
It also decides which starter questions can show
Declaring a page type has a second, visible effect: a chat bubble or question
bar with Show starter questions on carries only the
chips whose own page scope admits that page
type. Declare product and a Home-scoped question drops out. On a page
where you declare nothing, page scope is not applied at all and every enabled
question with text in the shopper's language is eligible, the same rule the
panel applies there, so the chips on the widget and the chips inside the panel
it opens always agree.
Call it again on every route change
Unlike position/backdrop, customData isn't a one-time setting read only
when the panel first opens; it can be set again at any time, including while
the conversation is already open. On a single-page-app storefront, call
iadvize('set', {customData}) again every time the shopper navigates to a new
route, not just once at initial page load. Otherwise the assistant keeps using
whichever page context you last set (every message sent while the panel is
open carries it) even after the shopper has actually moved to a different
page.
It's a reference, not a fact
The assistant treats customData as something to look up, never something to take at face value. Setting productId: 'XXX' doesn't make the assistant state that product's price, stock, or promotion status: it looks the product up in your catalog first, the same way it would if the shopper had just described it, and answers from what it actually finds there. categoryName works the same way: it's a hint the assistant folds into a catalog search, not an exact-match filter, so a category label that's close to but not identical to your catalog's own naming still works.
A shape that doesn't match is silently ignored
customData that doesn't match one of the four shapes above (a missing
categoryName on a category page, an unrecognized pageType, or a
categoryName/productId longer than 200 characters) is dropped: the panel
keeps working, the assistant just gets no page context for that turn. There's
no error or console warning, so double-check the shape if the assistant
doesn't seem to notice the page.
What the assistant reads from the page
The tag also reads the page on its own, with no code on your side. It's the fallback for every page where you don't call customData: a storefront that declares nothing still gets an assistant that knows where the shopper is standing. Unlike customData, which only ever describes the latest page, this travels with each message, so the assistant knows which page every question in the thread was asked from. A shopper who looks at one dress, moves to another, and asks "how does this one compare with the previous one?" is understood.
It's on for every site unless you switch it off, and there are two ways to do that (below). It applies to the tag on your storefront only: the public link page never carries it.
What is read
Three fields, nothing else:
| Field | Read from | Truncated at |
|---|---|---|
| Address | location.origin + location.pathname | 500 chars |
| Title | document.title | 200 chars |
| Description | <meta name="description">, falling back to <meta property="og:description"> when that tag is absent | 300 chars |
The address is rebuilt from the origin and the path, so everything after ? or # never leaves the browser: search terms, an order token, an ?email= parameter, your own campaign parameters. The rebuild runs twice, once on your page before anything is sent and once on our servers on arrival, so a value that somehow survives the first pass is still stripped. Whitespace is collapsed and over-long values are cut to the caps above rather than dropped, so a long title still yields a usable signal.
Two more things get dropped on arrival, silently, with the message still going through as normal: an address that isn't http or https, and an address whose origin isn't in the site's Allowed origins list.
Nothing is inferred from it
No page type, no product id, no category name is ever derived from the
address, the title or the description. The structured fields stay exactly what
they've always been: values you declare with customData. The three
fields above are read as plain text and treated the way customData is
treated, as something to look up. The assistant uses them to work out what
"this" refers to and to compose a catalog search; it resolves the product
through your catalog before stating a price, a stock level, a promotion, or
availability.
When it's read, and when it's sent
Reading happens in the shopper's browser, and only there:
- when the shopper opens the panel,
- on every client-side route change (
pushState,replaceState,popstate), - once more about a second after a route change, because a headless storefront often updates its title just after the address changes. Without the second read, the first message after a navigation could pair the new address with the previous page's title,
- whenever you call
iadvize('set', …), so acustomDataorpageSignalchange takes effect on the next message.
All of those happen only once the panel is open. On a page where the shopper never opens the assistant, nothing is read at all: no DOM access, so this feature costs your page load nothing.
Sending only happens with a message. There is no page-view beacon: a shopper who browses ten pages and never types anything sends us nothing at all. Each message carries the page it was asked from, and that page is stored alongside the message, under the same 12-month retention as the rest of the conversation.
Nothing is read on a checkout page
On a page you declared as checkout with iadvize('set', { customData: { pageType: 'checkout' } }), nothing is read, and a page the panel is already holding from a previous route is cleared. So a message sent from checkout carries no page rather than the last one. This follows your declaration: a checkout page that declares no customData is read like any other page.
What you declare still wins
customData and this signal travel side by side and are never merged. Where you declare a pageType, productId or categoryName, that declaration is authoritative for the page it describes, and the assistant is told so.
Two ways to switch it off
Per site, from the dashboard. Open Settings → Public access and use the Page signal card's Read the current page switch. It's on for every site by default. Like the rest of that page there's no Save step (it applies the instant you flip it) and no publish step, and it needs the sites:edit permission: without it the card shows the current state and no switch. Turning it off is enforced on our servers as well as in the tag, so a cached tag, a stale configuration, or a hand-rolled request can't send a page for a site that has it off.
Per page, from your own code. Nothing is read on that page from then on, and anything the panel is already holding is cleared:
iadvize('set', { pageSignal: false })Call it on account and order pages
Themes routinely put a customer's name or an order number in the page title
("My orders · Jane Doe"), and the title is read as it stands. Call
iadvize('set', { pageSignal: false }) on account, order-history and
order-detail pages. The query string is already stripped for you, which covers
most identifiers, but the title isn't.
On a single-page app, switch it back on
pageSignal: false holds for as long as the loader lives, not just for the
current route. On a classic multi-page storefront that's one page, so the call
affects only the page that makes it. On a single-page app, where no full
reload happens, call iadvize('set', { pageSignal: true }) when the shopper
navigates back out of the pages you excluded.
pageSignal: true only lifts a page-side false. It can't switch collection back on for a site whose dashboard switch is off: a call from your page can only reduce what's read, never widen it.
What the shopper sees, and what you see
The shopper-facing data-usage notice at https://www.iadvize.ninja/privacy/assistant/{lang} lists this as its own data category: which three fields are read, that the address stops before the ?, that it's read only when a message is sent and never while browsing, that it's kept with the conversation, and that the store can switch it off.
In the dashboard, a shopper message that carried a page shows an Asked from line under the bubble in the conversation transcript, linking to the page. A message with no page shows nothing.
Command API
Call iadvize(command, …). Calls made before the loader finishes are queued and replayed in order, so you can call it any time after the snippet runs.
Prop
Type
The events you can subscribe to with on:
conversation:opened: the panel opened.conversation:closed: the panel closed.message:sent: an auto-send message actually went out. No payload: if you need the text, you already have it, it's the same element/attribute your click handler triggered from.config:loaded: the tag fetched the site's display config (branding, locale); the config object is passed to your callback.config.localeis the language actually displayed (the result of Choose the shopper's language above), as a plain site language code (fr,en, …), not a regional dashboard locale. It used to carry a dashboard-style tag likefr-FR; if your own code readsconfig.locale, update it to expect the shorter code.
iadvize('on', 'conversation:opened', function () {
// e.g. fire your own analytics event
})Make sure it can actually answer
The tag loads from the key alone, but the panel only holds a conversation once the site has an agent ready to serve it:
- The site must have a default agent set (Settings → Public access → Default public agent), and
- that agent must have a published (champion) version.
Without both, the panel still opens but the conversation returns not-found: nothing answers. The Install the tag card warns you when no champion is ready. The panel always serves the default agent's current champion; promote a new champion and the tag picks it up with no snippet change.
Test the tag from a local page
You can try the tag on a locally-served test page before you touch your real storefront. The conversation iframe accepts being framed on localhost and 127.0.0.1 (over http://, any port), on top of every HTTPS origin.
That's only half of it, though: framing being allowed doesn't mean the panel will boot. Add your local origin to this site's allowed origins first (see above), for example http://localhost:8000, or the panel refuses it exactly like it would refuse any other unlisted origin.
Serve the test page over HTTP from localhost or 127.0.0.1. For a static HTML file, the quickest way is:
python3 -m http.server 8000
# then open http://localhost:8000/your-test-page.htmlYour storefront's own dev server works too, as long as you reach it at http://localhost:<port> (or http://127.0.0.1:<port>) and that exact origin is on the allowed-origins list.
file:// and LAN addresses won't work
The iframe is framed only from HTTPS origins plus localhost / 127.0.0.1.
Two common test setups fall outside that and the panel stays blank:
- Opening the file directly (
file:///…/your-test-page.html). Afile://page has no real origin, so the browser blocks the frame. Serve it overhttp://localhostinstead. - A LAN IP or custom local hostname:
http://192.168.1.20,http://my-shop.local,http://….test, or an HTTP tunnel. These aren'tlocalhost, and they're not HTTPS, so they're not allowed. Serve them over HTTPS to test from them.
This is purely a local-testing convenience. Real storefronts are HTTPS, which is always allowed, so nothing here needs setup on the iAdvize side.
Allow the tag through your Content Security Policy
If your storefront sends a Content-Security-Policy header, add the iAdvize origins so the browser doesn't block the tag. In production:
script-src https://tag.iadvize.ninja
frame-src https://www.iadvize.ninja
connect-src https://www.iadvize.ninjascript-src: the loader and its bundle are served from the tag host (tag.iadvize.ninja).frame-src: the conversation runs in an iframe on the app origin (www.iadvize.ninja/embed/<key>).connect-src: the loader fetches the site's display config and posts page telemetry to the app origin.
The iframe accepts being embedded on any HTTPS origin (its frame policy is frame-ancestors https:, plus http://localhost and http://127.0.0.1 for local testing). This is about browser framing, separate from the allowed origins the panel itself checks before it will start a conversation. A production HTTPS storefront needs no CSP changes on the iAdvize side; it does still need its own origin on your allowed-origins list. Use the exact origins printed in the snippet the dashboard gives you if your deployment differs from the production hosts above.
Voice dictation in the panel
When the served agent version has the Voice dictation capability turned on, the chat composer's microphone button (speech-to-text, transcribed entirely in the shopper's browser, no audio or transcript reaches iAdvize) works inside the embedded panel out of the box. The iframe our loader creates already requests microphone permission delegation; there's nothing to configure on your side for the common case.
A restrictive Permissions-Policy header can block it
If your storefront sends its own Permissions-Policy HTTP header that
restricts microphone for iframed content, that takes precedence over our
iframe's own delegation and the mic button won't work inside the embedded
panel, even though it works fine on the hosted
page, which isn't framed. If you hit this, add our
origin to that header's microphone allowlist for the app origin used in your
Content Security Policy
section above. Most storefronts don't send this header at all and need no
change.
Shopper privacy: the data-usage notice and the right to object
Every conversation (through the web tag or the public link) now links to a data-usage notice, shoppers can object to usage measurement from inside the chat panel, and a shopper can delete the conversation they're currently in. This section covers what ships, what it suppresses, and (just as important) what it doesn't do for you.
The data-usage notice
The panel's AI-disclosure line (the "You're chatting with an AI assistant. Responses may contain mistakes." text shown at the top of the conversation) now links to a notice (see the English version) at:
https://www.iadvize.ninja/privacy/assistant/{lang}{lang} is one of the six languages the assistant can converse in: fr, en, de, it, es, nl. The notice states, in plain language: what's collected (the conversation itself, the ownership cookie that keeps it going, a session-scoped measurement key, standard technical request data, and an anonymous performance measurement) and how long each is kept (conversations for 12 months, session-scoped measurement data for 24 months), that messages are sent to the LLM provider with zero data retention and an instruction not to train on the content, asserted on every request, that what the assistant stores durably is held in the EU while some processing happens elsewhere, and that iAdvize acts as processor on the store's behalf, the store being the controller.
The performance measurement. The chat panel on your storefront (the /embed iframe) sends real-user performance measurements, the Web Vitals of how fast the panel loads and responds, to our hosting provider. It sets no cookie and carries no identifier. The page address is cut down to its route, /embed/[key], so neither your site key nor any query string reaches the provider, and that includes the session-scoped measurement key. The panel sends no audience measurement (page views). The public link page behaves the same way, reduced to /s/[key]. The sub-processor list names the provider and where it processes this.
Shoppers can object to measurement
A second menu item, next to "New conversation" in the chat panel's header menu, lets a shopper object to (or withdraw an objection to) usage measurement, with a one-line explanation and a link to the notice. This is on by default: there's nothing to turn on for it to appear, on both the web tag and the public link.
Objecting stops, for that browser tab going forward:
- the session-scoped measurement key (cleared immediately if one was already set for this tab),
- the "was the assistant shown" beacon sent when the page loads,
- measurement rows (impression, click, conversation-opened) tied to that session, also checked on our servers for every request that carries the objection (see the timing caveat for an objection coming from your CMP),
- the panel's performance measurement to our hosting provider (see the timing caveat for an objection coming from your CMP).
Objecting does not delete data already recorded (it expires on its own retention schedule) and does not stop the conversation itself: the ownership cookie and the transcript are unaffected, since without them the assistant couldn't keep answering the shopper's own conversation. The objection is stored per store (on your own site's origin), so it doesn't carry over to a different store; there's no shared identifier across stores for it to carry over on.
Call it from your own CMP
The same objection is reachable as a command, so your own cookie-consent tool can drive it without a shopper ever opening the chat panel:
iadvize('privacy', { objected: true }) // shopper objects
iadvize('privacy', { objected: false }) // shopper withdraws the objectionLike every other command, it can be called any time, including before the loader script has finished loading: the call is queued and honored as soon as it boots.
One timing caveat. The panel is loaded the first time a shopper opens it on a page and then stays loaded until they leave that page. An objection your CMP signals after that first open takes effect on your page right away, but it doesn't reach the panel already loaded: that panel keeps the state and the session key it was opened with. Until the shopper's next page load, it can keep recording their interactions inside it (clicks, starter-question picks) under that session key and keep sending its performance measurement, whether it's open or closed at the time. Our servers keep no objection record of their own, so they can't catch this on their side. An objection made from the panel's own menu applies at once.
Shoppers can delete their own conversation
A third menu item, Delete this conversation, sits in the same header menu, on both the web tag and the public link. Confirming it permanently deletes the conversation and every one of its messages, then starts the shopper fresh on a brand-new one; there's no undo.
It only works while the shopper's browser still holds the small ownership cookie described in Cookies the tag sets below, the same cookie that lets the panel keep answering their conversation at all. Once that cookie is gone (a different browser or device, a cleared cache, or it having expired), that past conversation can no longer be singled out and deleted on request: there's no other identifier we can use to find it. It still falls under the retention period that applies to every conversation; see the data-usage notice above for how long that is.
There's no iadvize(...) command for this. Unlike the objection above, deletion isn't something your own CMP would trigger on a shopper's behalf.
This notice and these controls don't make you GDPR/ePrivacy compliant
The notice, the objection mechanism, and the deletion control are evidence you can point to when assessing your own compliance, but they are not a certification that your use of the assistant is compliant. iAdvize acts as a data processor on your instructions; you remain the data controller, responsible for your own privacy policy, your own cookie/consent banner, and whatever assessment your legal counsel or DPO makes about your specific use of the assistant.
Your obligation: informing visitors who never open the chat
The notice and the in-panel objection control only reach a shopper once they open the chat panel. The "was the assistant shown" measurement fires on page load, before that, so a visitor who never opens the chat is measured without ever seeing the notice or getting a way to object from inside the widget.
Covering that visitor is your obligation, not something this widget does for you:
-
Reference our notice from your own privacy policy. Link
https://www.iadvize.ninja/privacy/assistant/{lang}for each language your site serves, so a visitor who reads your policy (rather than opening the chat) is still informed. -
Wire the objection command into your own CMP's consent-change callback, so a visitor who withdraws consent through your own cookie banner is honored by the assistant too, even if they never open the chat:
// in your CMP's callback, whenever the visitor's consent choice changes: iadvize('privacy', { objected: true }) // consent withdrawn / refused iadvize('privacy', { objected: false }) // consent given / restored
iAdvize cannot enforce either of these for you
We can't edit your privacy policy, and we can't detect your CMP's consent
state on our own: the assistant only reacts to the privacy command you
choose to call. This is the processor/controller split: if you don't reference
our notice and wire this command, visitors who never open the chat aren't
informed or given a way to object outside the widget itself, and closing that
gap is your responsibility, not something we can do on your behalf.
A paragraph you can paste into your privacy policy
Here is the wording, in English and in French, describing exactly what the assistant does on your site. Paste the version matching the language your policy is published in, fill in your company name, and check it against the rest of your policy before publishing. It describes our side of the processing; it is not legal advice, and using it does not move the obligation above onto us.
[Your company name] uses the AI Shopping Assistant provided by iAdvize SAS (9 rue Nina Simone, 44000 Nantes, France) to answer visitors' questions on this site. iAdvize acts as our processor: it processes this data on our instructions and on our behalf, and we remain responsible for it.
When a page carrying the assistant loads, and before you open anything, a key scoped to your browsing session is stored in your browser and cleared when you close the tab. It is used to measure how the assistant is used on this site: it records that the assistant was available on the page, and, if you then open it, that a conversation started. It does not identify you and is not shared with other sites.
If you converse with the assistant, we also process the conversation itself (the messages you send and the assistant's replies) and a cookie that lets the assistant recognise your own conversation while you browse. That cookie is set when you send your first message, carries no other information about you, and is cleared when you close your browser.
Conversations are kept for 12 months after their last message. Measurement data is kept for 24 months after it is recorded.
iAdvize publishes a notice describing this processing on its own side at
https://www.iadvize.ninja/privacy/assistant/{lang}, where{lang}is one of the six languages the assistant converses in:fr,en,de,it,es,nl.You can object to this measurement at any time from the menu in the assistant's chat window, next to "New conversation". Objecting stops collection going forward. It does not delete data already recorded, which is covered by the retention periods above, and it does not stop the assistant from answering you.
Why this says 'kept for' and not 'deleted automatically'
12 and 24 months are the retention periods the product applies. The job that enforces them by deleting expired conversations and measurement data is built, but its daily schedule is not yet registered in production, so nothing is being purged on a timer today. Don't claim automatic deletion in a published policy until it is. Once the schedule is running, you can strengthen the wording above to say the data is deleted automatically at the end of each period.
Two things to adjust before you publish it:
- Add a sentence about your own consent tool only if you wired the
privacycommand above. Something like "You can also object from our cookie settings, without opening the assistant" / « Vous pouvez également vous opposer depuis nos paramètres de cookies, sans ouvrir l'assistant ». Without that wiring the sentence is false, so leave it out. - Link the notice URL for each language your site serves, replacing
{lang}with the actual language code.{lang}is a placeholder in the URL, not something the page resolves on its own.
Nothing in this paragraph covers the personal data you collect for your own purposes on the rest of your site. That stays yours to describe.
Rate-limit protection
The tag's chat requests go through the same endpoint as the public link, protected the same way. See that section for how it works.
Conversation continuity across page navigation
What happens to an open conversation depends on how the shopper moves around your site:
- In-app navigation (single-page apps). The panel and its iframe are never torn down as the shopper moves between routes, so the conversation just keeps going.
- A full page reload, a link to another page, or a new tab on the same site. The conversation resumes there too, and not because a cookie merely survived: it resumes because the assistant looks the shopper's own conversation back up and reopens it with its full history, the next time the panel is opened. This works the same way on the web tag and on the public link, and it's what a classic multi-page storefront (Shopify, PrestaShop, WooCommerce) needs, not just a single-page app.
Resume doesn't reopen the panel by itself: that's still entirely up to your shopper (or your own trigger), same as opening it any other way. What changes is what's waiting inside once it does open: instead of starting empty, it checks whether this same shopper already has a conversation running (same site, same agent, activity within the last 24 hours) and if so, picks it back up exactly where it left off, scrolled to the last message. Nothing announces this: no banner, no "welcome back," no new element. Starting a new conversation, or deleting one, still always starts empty.
Older Safari starts a fresh conversation on reload
Resume depends on the shopper's browser still holding the small cookie described in Cookies the tag sets below. On a browser that doesn't support it, chiefly Safari versions before 26.2, a full page reload or navigation starts a new conversation instead of resuming the old one, silently, with no error shown. This is a known, accepted limitation, not a bug: it affects a small and shrinking minority of browsers, and narrows further as those browsers update. There's no setting on your side that changes this.
A checkout on a different domain won't resume either
If your checkout runs on a different registrable domain than the rest of
your storefront (a separate domain some payment or checkout providers require,
not just a subdomain of your own site), the conversation doesn't carry over
there. The cookie that makes resume possible is scoped to one top-level site
by design (the same scoping that keeps it from following a shopper across
unrelated sites), so a domain change resets it exactly like switching browsers
would. A checkout on a subdomain of your own site (for example
checkout.yourshop.com alongside www.yourshop.com) is unaffected: that's
still the same site.
Cookies the tag sets
If your legal or technical team asks whether the widget sets cookies on your site: yes, one, and here's exactly what it does.
- What it's for. It's the small ownership token the assistant sets the moment a shopper sends their first message, so a later request can be recognized as the same conversation. That same cookie is also what makes the resume behavior above possible: it's used to look the conversation back up, not just to authorize it. No second cookie was added for that, and nothing about consent changed.
- What it isn't. It doesn't identify the shopper across visits, and it doesn't correlate them across different sites. It's cleared automatically when the shopper closes their browser; it's not a long-lived tracking cookie.
- Consent. Because its only purpose is to technically enable the conversation the shopper themselves started (not analytics, marketing, or advertising), it falls under the "strictly necessary" (functional) cookie exemption, the same category as a shopping-cart cookie. It does not require a cookie-consent banner or opt-in under GDPR/ePrivacy. That's the category name to cite if your legal team asks.
Not in this version yet
To set expectations for what you're embedding today:
- No inline, in-page engagement widgets. A block of chips woven into your page layout (inside a product page's content) isn't part of the web tag yet. The chat bubble and question bar are both fixed, floating overlays, and the starter-question chips they can carry render above the widget itself, not in your page flow. The dashboard's format catalog lists the in-page formats as Coming soon; they can't be created.
- No greeting or auto-invite. Both the chat bubble and the question bar are static, always-there surfaces: neither pops up, animates in, or sends a proactive first message on its own; a question bar only sends anything once the shopper types it themselves.
- No durable cross-session visitor profile. The measurement key is cleared when the tab closes; the tag doesn't build a shopper profile that persists or follows a visitor across sessions.
- No consent gate before the first message. The tag doesn't block a conversation until a shopper accepts cookies: objecting suppresses measurement, not the conversation itself. Wire your own CMP as described above if a consent choice needs to reach the assistant before a shopper ever opens the panel.
Each of these is planned separately and isn't part of the web tag today.
Starter questions
A site-wide library of tappable questions that open a conversation. Written once per language, shown on the chat welcome screen and on your engagement widgets, and applied the moment you save.
Style engagement widgets with CSS
The supported CSS surface on the chat bubble, the question bar, the inline button and Starters: eight custom properties, nine shadow parts, and what stays private.