Munin
Munin developer portal
Get a key →
Guide · Embeds

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.

Embed snippetpaste in <body>
<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:

Node.jscompute on every request
import crypto from 'node:crypto';
const userHash = crypto
  .createHmac('sha256', process.env.MUNIN_IDENTITY_SECRET)
  .update(externalId)
  .digest('hex');

Then on the script tag:

With identityvisitor binds to externalId
<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 on conv_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 agent message 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 both conv_contacts and end_users.
Typing indicator
The runner emits realtime typing events 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 human instead of AI.

Security

Origin allowlist
Each widget channel has an originAllowlist. The widget’s requests carry an Origin header; 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 externalId to 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.