Embedding the widget
The tag
Section titled “The tag”<script src="https://engadin.ai/widget.js?agent=your-agent" defer></script>Paste it once, before </body>, on every page. It loads deferred, so it never
blocks rendering, and it adds one launcher button in the corner.
The agent parameter is required, and it is the agent’s embed name from
the dashboard. Everything else is optional, and everything else can also be
changed later from the dashboard without touching the tag.
Options
Section titled “Options”Options can be given two ways, and the query string is the one to prefer:
<script src="https://engadin.ai/widget.js?agent=your-agent&position=left&lang=de" defer></script><script src="https://engadin.ai/widget.js" data-agent="your-agent" data-position="left" data-lang="de" defer></script>Tag managers and cookie-consent tools routinely re-create script tags and drop
data- attributes while doing it. Query parameters survive that, so they are
the safer form for anything loaded through Google Tag Manager or a consent
platform.
The full list is in widget parameters.
Checking that the tag is live
Section titled “Checking that the tag is live”Workspace → Deploy → Embed has a Check my site now button, and the setup checklist runs the same check on its install step. It loads a page of your website in a headless browser, runs the page’s scripts and reports what it finds — so an embed delivered by Google Tag Manager or a consent platform counts, even though nothing about it appears in your page source.
Three answers are worth knowing:
- The widget is on your site. The install step completes on this, exactly as it would on the first real visitor’s load.
- No widget yet, but a tag manager is on the page. Almost always one of three things: the container was never published, the tag has no trigger (it needs All Pages), or a consent rule holds it back until someone accepts.
- We could not see your site. Bot protection answered instead of the page, so the check has no opinion about your tag either way — see Bot protection. Open your own website in your own browser instead: the widget reports its first load itself, and the step completes on that.
The check reads only the website address stored on the agent, never a URL you
type, and it identifies itself in your access log with an
X-Engadin-Diagnostic header.
Loaded from two places at once
Section titled “Loaded from two places at once”Moving the tag from Google Tag Manager into your site’s HTML (or the other way round) usually means both are live for a while. That is safe: the first copy to run boots the widget, every later copy stops immediately, and visitors only ever see one launcher and one conversation.
The copy that stops writes a warning to the browser console naming both embeds, because a duplicate is not meant to stay:
[Engadin] This page embeds the chat widget twice — remove one of the two embeds. running: agent "your-agent" via a script tag in the page HTML — https://engadin.ai/widget.js?agent=your-agent ignored: agent "your-agent" via Google Tag Manager (or another tag manager) — https://engadin.ai/widget.js?agent=your-agent Inspect window.__engWidget for details.Open the console on any page of the site: no warning means only one embed is
left. window.__engWidget holds the same information for scripted checks — the
embed that is running, and a duplicates array of the ones that were ignored.
Position
Section titled “Position”position=left moves the launcher to the other corner. The offsets move it
away from something else already sitting there — a scroll-to-top button, a
cookie bar, a sticky cart:
<script src="https://engadin.ai/widget.js?agent=your-agent&offset-bottom=90&offset-side=16" defer></script>For a launcher that has to move only sometimes, your own CSS can drive it. The widget reads two custom properties and animates between values:
body:has(.to-top-button.is-visible) { --eng-offset-bottom: 96px;}Left alone, the launcher sits 24px from both edges on a desktop and 12px from both on a screen narrower than 480px, where that corner is page content rather than margin. An offset you set — on the tag or in your own CSS — replaces the default at every width, so a widget you moved clear of your own sticky bar stays where you put it on a phone too.
Phones and tablets
Section titled “Phones and tablets”On a small screen the bottom corner is usually already spoken for (a sticky cart, a cookie bar, a call button), and moving the launcher only trades one collision for another. Deploy → Chat widget → Chat window → Show the widget on phones and tablets switches it off there instead, and leaves desktop untouched.
Off, phone visitors get no launcher and no teasers: the page stays exactly as it was before you embedded anything. A tablet counts as a phone, the same rule the conversation list and the analytics device split use.
The tag itself still loads and still asks for its configuration, which is what makes switching the widget back on take effect on the very next page load rather than whenever a visitor’s browser gets around to forgetting the old answer.
Two things deliberately ignore the setting, because both are placements
someone made on purpose rather than a launcher that turned up on its own: the
hosted chat page, and an iframe embed of it. A link to /chat?agent=... keeps
working on a phone, which is what makes this a reasonable trade: you can still
put the chat one tap away, in a place you chose, from your own menu or
footer.
Taking the conversation away
Section titled “Taking the conversation away”The chat menu (the ··· in the header) can offer the visitor two ways to keep what they were told, both under Deploy → Chat widget → Chat window:
- Download this chat saves the conversation as a text file. It is built in the visitor’s own browser from the messages already on screen, so nothing leaves the page and no mail is sent.
- Email this chat to me asks for an address and sends the transcript there — from your own verified sending domain when the email channel is set up, otherwise from the platform, under your assistant’s name either way. The mail is written in the language the chat was held in.
Both are on by default and either can be switched off on its own. The email entry also disappears by itself when no mail is configured for the platform, so a visitor is never shown a form that could only fail.
Sending is capped at three transcripts per address per hour, which is what stops the feature from being usable to mail a stranger repeatedly. A visitor asking for their own conversation twice never notices it.
Colors
Section titled “Colors”Colors live in the dashboard, under Deploy → Chat widget → Style: your brand color, a color for the round launcher, light or dark for the chat panel, and the header, which can keep the built-in navy, follow your brand color, or take any color you type. Everything applies to the live widget the moment you save.
The brand color is used the way a design system would use it rather than painted on everything: the visitor’s own message bubbles are a soft tint of it, so a conversation does not turn into a column of solid color, while the send button and the launcher take it at full strength because those are controls meant to be found. The text on the header, and the agent’s name on it, are computed from whatever the header ends up being, so they stay readable on a color we have never seen.
You do not have to find those values yourself. Match my website loads your home page in a browser, reads the colors the page actually paints (its brand tokens, the color on its buttons, its own background) and offers them as ready-made palettes, with the screenshot it looked at shown next to them so you can check the judgement.
Each palette is a whole look, not a single color: brand, launcher, header and light-or-dark together. Clicking one puts it straight into the live preview, and Save publishes it, so choosing a set of colors is two clicks. The recommended palette is applied for you the moment a look finishes.
The last look is kept, so opening the tab again shows what was found without looking again. Look again is there for when your site has changed; it is the only thing that spends another page load.
Two things it corrects on its own, and tells you about: a launcher color that would be invisible against your page is pushed until it is not, and a brand color the header text could not be read on becomes a deeper shade of itself for the header rather than falling back to navy.
The full-page chat
Section titled “The full-page chat”Every agent also has a hosted chat page. Link to it, or embed it:
<iframe src="https://engadin.ai/chat?agent=your-agent&embed=1" style="width:100%;height:640px;border:none;border-radius:16px" allow="microphone"></iframe>embed=1 renders the compact variant, without the conversation sidebar.
allow="microphone" is what lets voice work inside the frame — without it the
browser blocks the microphone regardless of the visitor’s permission.
The chat page runs the same widget, so it wears the same colors. Deploy → Chat page has its own Colors card with the same palettes described above; a palette chosen there saves straight away and applies to both. The individual fields and the live preview stay on the widget’s Style tab.
Two parameters are worth knowing for iframes:
page=<url>— the real page the frame sits on, so conversations are attributed to it rather than to/chatq=<question>— pre-fills the input with a question. It is only typed in, never sent; the visitor still presses send.
Opening the chat from your own UI
Section titled “Opening the chat from your own UI”Any element on the page can open the chat, with no JavaScript of yours:
<button data-engadin-open>Ask a question</button><a href="#" data-engadin-open="What does this cost?">Ask about pricing</a>Giving the attribute a value pre-fills that question. A plain link from elsewhere on your site can do the same with a query parameter:
<a href="/pricing?engadin_q=Which+plan+fits+a+small+team%3F">Ask the assistant</a>Suggesting a question from JavaScript
Section titled “Suggesting a question from JavaScript”The widget publishes a two-method API on window:
window.engadin.open('Do you ship to Switzerland?'); // opens, types the questionwindow.engadin.open(); // opens, empty inputwindow.engadin.open({ prefill: 'How much is the Pro plan?' });window.engadin.close();This is how you turn any part of your own interface into an entry point that arrives with context. A product page can offer the question that page provokes, a pricing table can offer the one its own rows never quite answer, and a failed form can offer to ask about the thing that failed:
document.querySelector('#size-help').addEventListener('click', () => { window.engadin.open(`Which size fits me for the ${product.name}?`);});
document.querySelector('#checkout-error').addEventListener('click', () => { window.engadin.open('My payment was declined at checkout. What now?');});Three details worth knowing:
- Calls before the widget has booted are queued, not lost, so you do not
have to wait for
widget.jsto finish loading before wiring your buttons. - Text is normalised and capped at 600 characters. Whitespace collapses.
- A question can be built from your own state, as in the examples above. Nothing is sent to the server until the visitor sends it.
For the iframe embed the API lives inside the frame, so the host page talks to it by message instead:
const frame = document.querySelector('#engadin-chat');frame.contentWindow.postMessage( { type: 'engadin-prefill', prefill: 'Do you have a student discount?' }, 'https://engadin.ai' // the origin serving the chat);Same behaviour: it types, it does not send. Reloading the frame with a
different q= parameter would work too, but the message avoids throwing away
the conversation on screen.
Analytics events
Section titled “Analytics events”The widget reports what happens as it happens, in three forms at once: a
window.dataLayer push named engadin_<event> for Google Tag Manager, a DOM
CustomEvent called engadin on window, and a postMessage to the parent
window for iframe embeds.
window.addEventListener('engadin', (e) => { const d = e.detail; console.log(d.event, d.agent); // 'message_sent', 'your-agent'});Payloads carry the event name, the agent id, and small facts: how the chat was opened, a rating value, an action slug, a clicked link. Never message content, never anything a visitor typed. The full list with its fields is in widget parameters.
In Google Tag Manager
Section titled “In Google Tag Manager”The pushes are ordinary custom events, so nothing beyond GTM’s own UI is needed:
-
Trigger. New trigger, type Custom Event. Event name
engadin_lead_createdfor one event, or tick use regex matching and enterengadin_.*to catch them all in a single trigger. -
Variables. New variable, type Data Layer Variable. Dot notation reaches into the payload:
Variable name Yields engadin.eventThe event name without the prefix, lead_createdengadin.agentThe agent’s embed name engadin.viaOn chat_opened:launcher,teaser,auto,deeplinkorapiengadin.ratingOn message_rated:upordownengadin.reasonOn rating_reason:wrong,incompleteoroutdatedengadin.actionOn the action_*events: the custom action’s slugengadin.urlOn link_clicked: the destination -
Tag. A GA4 event tag is the usual destination. Send
{{engadin.event}}as the event name, or map the ones you care about to GA4’s own names,generate_leadforlead_createdbeing the obvious one.
Without a tag manager, forward them yourself:
window.addEventListener('engadin', (e) => { const d = e.detail; if (d.event === 'lead_created') gtag('event', 'generate_lead', { agent: d.agent }); if (d.event === 'conversation_started') gtag('event', 'engadin_conversation', { agent: d.agent });});For iframe embeds
Section titled “For iframe embeds”Inside a frame the events fire in the frame, so they need a bridge on the host page:
<script src="https://engadin.ai/widget-events.js" defer></script>With that script next to the iframe, the same engadin_* dataLayer pushes and
engadin DOM events appear on your page. The script-tag embed does not need
it, because it already emits both directly.
Removing it
Section titled “Removing it”Delete the tag. Nothing is left behind on the page. Conversations already recorded stay in the dashboard until you delete the agent.