Drop-in chat widget.
One <script> tag on your site gives visitors a chat launcher that opens a Munin conversation. The widget runs inside a Shadow DOM, so your host page’s CSS can’t bleed in and ours can’t bleed out.
Quick start paste & ship
Mint a widget key from Settings → Channels, pick the channel, copy the embed snippet, paste it on every page where the launcher should appear.
<script src="https://api.getmunin.com/widget.js"
data-munin-host="https://api.getmunin.com"
data-widget-key="mn_widget_…"
data-channel-id="cch_…"
data-munin-fonts="system"
defer></script>Required attributes
- data-munin-host
- Origin of your Munin backend. The widget calls the REST API and the realtime WebSocket against this host. No trailing slash.
- data-widget-key
- Channel-bound API key starting with
mn_widget_. Shown once when you create or rotate a widget channel. Safe to embed on the public page — the key only authorizes the channel it was minted for. - data-channel-id
- The channel the visitor will talk to. Must match the channel the widget key was minted for.
Optional attributes
Add any of these to the script tag to customize the widget.
- data-munin-fonts
"system"(default) skips the bundled WOFF2 fonts and falls back to the visitor’s system stack (SF Pro / Segoe UI / Roboto + ui-serif). Set to"bundled"to ship subset Instrument Serif + JetBrains Mono with the widget — adds ~60 KB and matches the dashboard typography pixel-for-pixel.- data-munin-org-name
- Header title shown in the panel. Defaults to
"Chat". - data-munin-eyebrow
- Small uppercase label above the welcome greeting, e.g. “Acme Support · powered by Munin”.
- data-munin-theme-color
- Hex accent color for the launcher, send button, and visitor bubbles. Defaults to
#0066FF. - data-munin-position
"bottom-right"(default) or"bottom-left".- data-munin-size
- Panel size:
"compact","standard"(default), or"generous". - data-munin-greeting
- First line on the welcome screen. The widget splits on the first sentence so the second clause renders in italic, matching “Hi there. How can we help?”.
- data-munin-show-history
- Set to
"false"to hide the past-conversation list on the welcome screen.
Identity verification optional
If you want to bind a chat thread to a known user (so they resume their conversation on the next visit, even from a different device), compute a server-side HMAC with the channel’s identity secret and pass it as data-user-hash:
import crypto from 'node:crypto';
const userHash = crypto
.createHmac('sha256', process.env.MUNIN_IDENTITY_SECRET)
.update(externalId)
.digest('hex');Then on the script tag:
<script src="https://api.getmunin.com/widget.js"
data-munin-host="https://api.getmunin.com"
data-widget-key="mn_widget_…"
data-channel-id="cch_…"
data-external-id="user_42"
data-user-hash="<hex digest>"
defer></script>Without identity, the widget identifies the visitor by a UUID kept in localStorage (with a cookie fallback) — refreshes resume the same thread, but a different browser starts fresh.
Visitor profile
Pre-populate the visitor’s name, email, and arbitrary metadata so they show up immediately on the contact row. Useful for logged-in customers.
- data-munin-visitor-name
- Display name, max 120 chars.
- data-munin-visitor-email
- Email address. Validated client-side; re-validated by the server on every request.
- data-munin-visitor-meta
- Flat JSON object of string/number/boolean key-values, max 4 KB, e.g.
'{"plan":"pro","accountId":"acc_42"}'. Lands onconv_contacts.metadata. - data-munin-meta-<key>
- Sugar form of the above — every
data-munin-meta-*attribute becomes a metadata key.data-munin-meta-plan="pro"≡{"plan":"pro"}.
What it does
- Welcome screen
- Shown on launcher open: greeting, “Start a conversation” CTA, and the visitor’s past conversations. Identity-verified visitors see every thread bound to their
externalId; anonymous visitors see only threads from session-IDs remembered locally. - AI greeting
- When the visitor clicks “Start a conversation”, the widget creates the thread server-side and the AI runner generates an opening turn from the system prompt. The visitor sees the three-dot indicator while the LLM is working, then the greeting lands as a real
agentmessage stored in the conversation. - Email capture
- After the first agent turn, an inline card prompts the visitor to share their email so the operator can follow up if the visitor closes the tab. Submitted via
PATCH /api/v1/widget/visitor, persisted on bothconv_contactsandend_users. - Typing indicator
- The runner emits realtime
typingevents while it’s generating, with a 3-second keepalive so the indicator stays alive through long replies. Server auto-clears after 5 seconds of silence; widget auto-clears locally after 5 seconds as a fallback. - Handover to a human
- When an operator takes the conversation (manual claim or agent-requested escalation), the widget’s chat subtitle flips from “Munin AI · instant” to the operator’s name, and subsequent agent bubbles are tagged
humaninstead ofAI.
Security
- Origin allowlist
- Each widget channel has an
originAllowlist. The widget’s requests carry anOriginheader; the server rejects requests from any origin not on the list. No allowlist = allow all origins (useful for staging). Set it before going to production. - Identity verification
- The HMAC pairs an
externalIdto a digest signed with the channel’s identity secret. Without this, a visitor can’t claim someone else’s identity — they get an anonymous session bound to their local sessionId. - Require verified identity
- Toggle on the channel config to reject anonymous traffic entirely. Useful for in-app embeds where every visitor is signed in.
- Shadow DOM
- The widget tree lives in an open shadow root. Host-page CSS doesn’t reach in, and the widget’s styles don’t reach out. Custom fonts are registered at the document level so they cross the shadow boundary cleanly.