-
Notifications
You must be signed in to change notification settings - Fork 15
Web Chat
Put a chat window on any website and every conversation becomes a normal FreeITSM ticket. A visitor drops a few lines of <script> onto their site, a floating chat launcher appears, and their messages land in the inbox in the right company — answered by analysts from the same reading pane they use for email and WhatsApp. It's a third inbound channel that reuses the whole ticket pipeline.
Status. Live: the embeddable widget, the pre-chat name+email prompt, visitor → ticket, analyst replies delivered back to the visitor, per-company routing, the full security model (public key + origin allowlist + per-conversation token + rate limiting), and — as of part three — office hours, AI answers (assist/deflect) and escalation (talk to a person / email me back). The one piece still to wire: sending the offline reply out from the company's service-desk mailbox (
email-when-away); the toggle saves but delivery isn't built yet. This page documents the whole design, marking what is live versus planned.
A web chat widget is the self-hosted twin of a WhatsApp number: it drives one messaging channel (channel_type = 'webchat', provider = 'freeitsm', no external provider or credentials), so once a message arrives it flows through the same ticket, inbox, thread and reply machinery as every other channel. There is nothing new on the analyst's side — a web chat ticket looks and behaves like a WhatsApp ticket.
- The visitor opens the widget and (if the widget asks for it) enters their name and email — the pre-chat gate, so every ticket has a real requester.
- They type a message. It's ingested onto the ticket membrane as a Web chat-origin ticket, pinned to the widget's company.
- It appears in the inbox exactly like any other ticket. The analyst replies from the normal reading-pane composer.
- The reply is delivered back into the visitor's chat window (the widget polls for new messages every few seconds).
- One conversation is one ticket; further messages thread into it.
Two sides: create the widget inside FreeITSM, then paste one snippet onto the website.
The embed snippet points browsers at your FreeITSM host to load widget.js. FreeITSM builds that URL from the Public base URL — the same setting the WhatsApp channel uses, at the top of Tickets → Settings → Messaging. Set it to the address the outside world reaches this install on (e.g. https://itsm.yourcompany.com). If it's blank, FreeITSM falls back to whatever address you're browsing from, which is fine for local testing but not for a real embed. The host must be reachable over HTTPS from the customer's site, and served over HTTPS itself (browsers block a chat widget loaded over plain HTTP on a secure page).
Tickets → Settings → Web chat → Add. Fill in:
- Name (e.g. "Acme website"), and on a multi-company install the Company these conversations belong to.
- Greeting, accent colour, launcher text — the look of the widget.
-
Allowed website(s) — one origin per line, e.g.
https://www.acme.com(andhttps://acme.comif both are used). Only these sites may show the widget. Leave blank only while testing. - Leave Ask for name + email first on unless you have a reason not to.
- Optionally set office hours, email-when-away and AI answers (see Settings).
Click Save. FreeITSM mints the widget's public key and shows the embed snippet right there in the modal — ready to copy.
The snippet looks like this (your real key replaces the placeholder):
<script>
(function(d){
var s=d.createElement('script');
s.src="https://itsm.yourcompany.com/api/webchat/widget.js";
s.async=true;
s.setAttribute('data-freeitsm-widget',"wc_XXXXXXXX"); // your public widget key
d.head.appendChild(s);
})(document);
</script>Paste it just before the closing </body> tag on every page that should show the chat window (in most CMSes this is a "custom code / footer scripts" box, so it applies site-wide). The widget renders itself inside a Shadow DOM, so the host site's CSS can't touch it and vice-versa — it looks the same on any site.
Reload the site — the launcher appears in the corner. Send a test message; it should arrive in Tickets as a Web chat-origin ticket in the chosen company. Reply from the reading pane; the reply appears back in the widget within a few seconds.
If the launcher doesn't appear, it's almost always the Allowed website(s) list not matching the site's real origin (scheme + host, no path, no trailing slash), or the Public base URL pointing somewhere the browser can't reach. Both are deliberate: a widget that isn't on an allowed origin is refused.
Never regenerate the key casually — it's baked into every page carrying the snippet. There's no "rotate key" button today; deleting and recreating a widget mints a new key and means re-pasting the snippet everywhere.
Managed under Tickets → Settings → Web chat (its own tickets.webchat permission, separate from the WhatsApp credentials tab — the widget key is public, so there are no secrets to protect here).
| Control | What it does |
|---|---|
| Name | Internal label; also the title shown on the chat panel. |
| Company | Pins every conversation from this widget to one company (multi-company installs). |
| Greeting | The opening line shown when the chat opens. |
| Accent colour | Hex colour for the launcher, header and the visitor's message bubbles. |
| Launcher text | The label on the floating button (e.g. "Chat to us"). |
| Allowed website(s) | The site origins permitted to embed this widget (one per line). Empty = any site (testing only). |
| Ask for name + email first | The pre-chat identity gate. On by default, so every ticket has a real requester. |
| Control | What it does |
|---|---|
| Office hours | Pick an existing SLA business-hours calendar, or "Always open". Outside those hours the widget shows your offline message and still takes the enquiry as a ticket to answer later. |
| Control | What it does |
|---|---|
| Email replies to the visitor if they've left the chat | If an analyst replies while the visitor is no longer watching the chat, send that reply to the email they gave, so the conversation continues by email. Requires a mailbox for that company (see Email direction). |
| Control | What it does |
|---|---|
| Answer from the Knowledge base with AI | When a visitor asks a question, AI drafts an answer before a person is involved — from this widget's company's articles plus shared ones, and only those marked public (Knowledge). |
| When AI answers | Assist — always raise a ticket as well. Deflect — only raise a ticket if the visitor escalates. |
| Offer a live agent | The AI always lets the visitor ask to speak to a person. |
| Offer a ticket by email | The AI lets the visitor raise a support ticket — created with an AI summary in the body and the full chat log attached as a .txt file. |
This is the part worth understanding, because a chat widget is the only bit of FreeITSM a random member of the public can reach.
The widget key is public, not a secret. It ships in the customer's page source — anyone can View Source and read it. So it is treated as a public address label, never a password. It grants exactly one thing: the ability to start a conversation, and only from an allowed origin. It cannot read tickets, cannot read anyone's chat, and grants no admin.
A per-conversation token is the real capability. When a visitor starts a chat, they get a private token stored only in their browser. Every send and every poll must present it. Reading or posting to an existing conversation needs that token — so even though the widget key is visible to everyone, one visitor can never read another visitor's chat.
Origin allowlist. Each widget lists the website addresses allowed to embed it. Requests are checked against it using the browser's Origin header, falling back to the Referer origin for same-origin embeds (a page served from the same host as FreeITSM sends no Origin). If someone copies your key onto evil-site.com, the origin won't match and it's refused. This is a strong deterrent against key-copying — not a cryptographic guarantee, since Origin/Referer can be absent outside a real browser, which is exactly why the per-conversation token, not the origin, is what protects a conversation's contents.
Rate limiting. New conversations are capped per IP per minute, and messages per conversation per minute, to blunt abuse of the public endpoints.
A common worry: if we email the customer (someone@gmail.com), aren't we spoofing gmail? No. The customer's email is only ever a recipient, never a sender. Any outbound email goes from your own company service-desk mailbox (the same authenticated Microsoft/Google/SMTP mailbox that handles that company's email tickets), to the customer — an ordinary reply from your domain, identical to answering an email ticket.
The practical consequence: offline-email only works if that company has a mailbox configured, because a web chat ticket arrives through the widget, not through a mailbox, so FreeITSM has to pick the company's service-desk mailbox to send from. No mailbox → nothing to send from → the toggle can't act.
The AI "raise a ticket by email" route doesn't send anything at the moment it's raised — it just creates a ticket (customer as requester, AI summary as body, chat log attached). Outbound only happens later if an analyst replies, again from your mailbox.
A widget is pinned to a company, so its conversations are isolated to that company exactly like WhatsApp numbers and mailboxes. On a single-company install none of this is visible — it behaves as you'd expect.
Knowledge base scoping. The AI answers only from the widget's own company's articles plus ones shared with every company, and only from articles an author has marked public. See Knowledge.
Every article defaults to "Analysts only" — including ones written before that setting existed. So after upgrading, the widget's AI stays quiet until you mark some articles public. That is deliberate: nothing written for internal eyes reaches a stranger unless someone chooses it.
| Area | State |
|---|---|
| Embed widget (launcher + Shadow-DOM panel), pre-chat name/email, polling delivery | ✅ Live |
Public endpoints (config / start / send / poll) + full security model |
✅ Live |
| Visitor → ticket in the right company, analyst replies back to the visitor | ✅ Live |
| Settings: appearance, origins, availability, email, AI toggles (save/read) | ✅ Live |
| AI answers (assist/deflect), office-hours gating, escalation (agent / email) | ✅ Live |
| Email-when-away — actually sending the reply out from the company mailbox | 🚧 Next build — toggle present, delivery not yet wired |
For the engineering detail — tables, endpoints, the channel reuse, and the resume plan — see the Web Chat: Developer Guide.
AI answers, office-hours gating and escalation shipped in part three. What's left, in rough priority order:
- Email-when-away delivery — the only remaining part-three piece. When an analyst replies and the visitor has left (or on an "email me back" escalation), email the reply from the company's service-desk mailbox to the visitor (requires that company to have a mailbox; see Email direction). The ticket and AI summary are already created — this is purely the sending step.
-
Per-company Knowledge scoping— done. AI answers are scoped to the widget's company + shared articles, and to articles marked public. See Knowledge. - Live delivery upgrade — replace polling with SSE for instant replies without the 3-second poll.
- Nice-to-haves — a proper "rotate key" action; letting the analyst's name (rather than the channel name) label agent replies in the widget; typing indicators / unread badges; file uploads from the visitor.
See also the project_webchat_channel memory note and the Developer Guide → Resume plan.
- WhatsApp channel — the sibling channel this reuses
- Web Chat: Developer Guide
- Tickets · Multi-Tenancy · AI Providers · Knowledge · SLA Management
FreeITSM — an open-source IT Service Management platform · github.com/edmozley/freeitsm · MIT licence
- Installation
- ⏰ Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- ⌨️ Command palette (⌘K)
- 🔍 Searching inside tickets
- 📄 Attached documents
- Mobile‑Friendly
-
Security
- Layer 1 — which modules you can enter
- ↳ 🧩 Module Access Control
- ↳ 🛠️ Module Access — Developer Guide
- Layer 2 — what you can administer
- ↳ 🎭 Roles & Permissions
- ↳ 🛠️ Roles — Developer Guide
- ↳ 🔤 Why capabilities are constants
- Layer 3 — the System module
- ↳ 🔑 Admin Access Control
- Hardening
- ↳ 📄 Security review response 2026-08
- ↳ 🛡️ Security hardening 2026-08
- ↳ 🛠️ Security hardening 2026-08 — Developer Guide
- ↳ 🛡️ Round three — plain English
- ↳ 🛠️ Round three — Developer Guide
- Single Sign-On (SSO)
- 🗂️ LDAP & Active Directory
- Browser Extension
- API Reference
-
🔌 REST API — how it works
- ↳ 🎫 REST API: Tickets
- ↳ 💻 REST API: Assets
- ↳ 🔴 REST API: Problems
- ↳ 🟠 REST API: Changes
- ↳ 📚 REST API: Knowledge
- ↳ ✅ REST API: Tasks
- ↳ 🗄️ REST API: CMDB
- ↳ 📜 REST API: Contracts
- ↳ 🗓️ REST API: Calendar
- ↳ 💿 REST API: Software
- ↳ 🚦 REST API: Service Status
- ↳ ☀️ REST API: Morning Checks
- ↳ 📝 REST API: Forms
- ↳ ⚙️ REST API: Workflow
- ↳ 🗺️ REST API: Network Mapper
- ↳ 🧭 Using the API docs page
- ↳ 📐 OpenAPI specification
- ↳ ✅ OpenAPI: kept correct
- ↳ 🛠️ Maintaining the catalogue
- Watchtower
-
Tickets
- ↳ Mailbox Authentication
- ↳ 📤 Email send log
- ↳ Basic IMAP mailboxes
- ↳ Email rendering & images
- ↳ SLA Management
- ↳ WhatsApp channel
- ↳ 💬 Web chat channel
- ↳ 🟣 Slack channel
- ↳ 🔗 Linking tickets
- ↳ 🗒️ Canned responses
- ↳ ✉️ Limiting replies to particular senders
- ↳ ✍️ Email signatures
- ↳ 🌐 The public web address
- ↳ 🙋 Raising a ticket for someone else
- ↳ 🔀 Merging tickets
- ↳ ⑂ Splitting tickets
- ↳ ✅ Selecting several tickets
- ↳ 🛠️ Snoozing tickets — Developer Guide
- ↳ 👥 Collision detection
- ↳ ⏱️ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- 🔔 Notifications
- 🚨 War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- ↳ 📊 Progress tracker
- ↳ Concepts & vocabulary
- ↳ Email routing & mailboxes
- ↳ Settings: global vs per-company
- ↳ Users & self-service
- ↳ Staff cross-company access
- ↳ Worked examples
- ↳ Pitfalls & gotchas
- ↳ Scope: what it's for
- ↳ 🛠️ Developer Guide (make a module multi-company)
- ↳ 🗄️ Case study: CMDB (a linked graph)
- ↳ 🧪 Test harness (prove it's isolated)