Skip to content

Live Chat

Chris Taylor-Guest edited this page Oct 13, 2026 · 21 revisions

Live Chat (Powered By Chatwoot)

The live-chat module gives a Cactus site real live chat: visitors talk to staff from a bubble on the site, attachments travel both ways, and staff answer from whichever surface suits - the Cactus admin, a floating console while browsing the public site, or the Chatwoot mobile app (with push notifications).

Where it lives now. Live chat used to have its own sidebar link. Conversations now sit on the shared Inbox screen, on a Live chat tab beside your contact-form enquiries. Old links still work - they just carry you to the tab.

Repo: cactus-foundation-modules/live-chat-powered-by-chatwoot

Want something simpler? Live Chat needs no chat server at all: conversations live in your own database. The two can be installed side by side.

How it fits together

  • The site (Vercel) carries only the thin module: the widget block, the admin inbox, and API routes.
  • The chat engine is a self-hosted Chatwoot server on a single Fly.io machine (Rails + Sidekiq + Redis + a small backup endpoint in one VM, auto-suspends when idle and wakes in seconds). Visitors' browsers talk to it directly.
  • It only runs, and bills, while it is needed. Fly suspends the machine once nothing is connected to it, and wakes it the moment something is: a visitor opening the chat, a staff member opening the console or the inbox, the Online/Offline switch, the nightly backup. Ordinary page views never touch it - whether anyone is on is kept in the site's own database (written by the Online/Offline switch) and read from there. Staff tabs only hold a live connection while the console is open or the inbox page is on screen; a closed console keeps its unread badge current from the site's own copy of the chats instead. The Chat server card under Settings → Live Chat reads the machine's state from Fly without disturbing it, so it shows asleep when it is; the chat backups list on Settings → Backup does wake it, as it has to ask the machine what it holds.
  • Chatwoot's database is a separate, ordinary Postgres database - never the site's own. The module mirrors conversations/messages into its lc_ tables (fed by Chatwoot webhooks) so the admin inbox lists and polls locally.
  • Realtime in the admin is the browser's own WebSocket to the chat server's ActionCable, using a limited pubsub token (the full agent token never reaches the browser). A 15-second poll of the mirror is the fallback.

The two places (read this first)

Everything day-to-day happens in the Cactus admin: answering chats, canned replies, settings, backups, the notification bell. Underneath sits a small separate chat engine (the Chatwoot server, e.g. chat.your-site.co.uk) that the widget, the admin inbox, and the phone app all talk to. You rarely open the engine's own website - its two real jobs are:

  • being what the Chatwoot mobile app signs into (server address + the chat login), which is where push notifications come from;
  • handing a staff member their personal Access Token if they want replies under their own name.

The chat login is created by the setup wizard for whoever runs it, and stays visible under Settings → Live Chat → Chat server login (password revealed only to admins with the manage permission). Two identities exist on the engine: the everyday owner login (use this one), and a super-admin maintenance login that is deliberately not a member of any chat account - signing into the normal dashboard with it shows "No account found", which is correct; its console lives at /super_admin and is for emergencies only.

Setting it up (self-serve installs)

  1. Install the module, open Settings → Live Chat.
  2. Paste a Fly.io API token and the connection URL of an empty Postgres database, pick an app name, and press Build my chat server. The module provisions the whole thing - app, volume, database schema, machine, IPs, inbox, webhook - in a couple of minutes.
    • The machine runs the standard de-branded Chatwoot build published publicly at ghcr.io/cactus-foundation-modules/chatwoot:latest. An optional Chat server image field on the same form takes a different image reference (your own build, a pinned tag); leave it blank otherwise. LIVECHAT_IMAGE on the Vercel project changes the default for that install.
  3. Add the Live Chat block to a sitewide layout (usually the footer/header layout row).
  4. Each staff member pastes their personal Chatwoot access token under My agent identity so replies carry their own name, and installs the Chatwoot mobile app pointed at the chat server for push.

Installs managed centrally can instead be pre-provisioned with LIVECHAT_* environment variables (see Configuration reference); env always wins over the settings UI.

Privacy behaviour (deliberate)

  • Chat itself is consent-gated, and says so. Where the site's consent banner carries a live-chat cookie category, nothing chat-related runs until that category is granted - no script, no cookie, no contact record. The bubble still shows, but clicking it opens a short notice instead of a conversation: Live chat needs a cookie, worded one way for someone who has not answered the banner yet and another for someone who answered and left live chat off, with a button underneath that reopens the cookie preferences on their existing choices. Tick live chat, save, and the chat they asked for opens on its own - no second click, no reload. Withdraw the permission mid-visit and an open panel closes, the widget is dropped, and this visit's buffered journey is forgotten.
    • Earlier versions hid the bubble entirely in this state. A missing bubble reads as "this site has no live chat", not "live chat is waiting on you", and left a visitor who had declined with no way back short of hunting for a cookie settings link.
    • Needs core v0.5.1098 or newer: on older core the reopened banner shows the site defaults rather than the visitor's own choices, so saving from it would quietly reset their other categories.
    • The category is added on admin → Config → GDPR & Legal, from the Suggested by active modules row (key live-chat). Adding it re-prompts existing visitors once. Without it, the banner has nothing for visitors to decide, so the bubble behaves as it always did and shows to everyone.
    • Staff with livechat.view get the agent console instead, which is a signed-in staff tool and is not gated this way.
  • Nothing loads until the visitor clicks the bubble. No script, no cookies, no contact record. Once clicked, the conversation opens directly - no "Start conversation" interstitial - it follows the visitor across pages while open, and the chat window follows the site's light/dark mode.
  • When the switch says Offline, the widget says so too - and asks for an email address. The bubble changes to Leave us a message (or your own words: Settings → Live Chat → Widget → Bubble label when you are away) and opens straight onto the message box, as it does when you are online. (It used to open on a front screen explaining you were away; visitors pressed past it unread and left messages nobody could answer.) The first message someone sends while you are away gets one reply, unless the chat already has their email address: We are away at the moment - please leave your email address and we will email you back. The wording is yours: Settings → Live Chat → Widget → Reply to a message left while you are away. An email address they type into the chat is saved against them, so your answer can reach them after they have gone. While you are away the chat's own "Give the team a way to reach you" box steps aside, so they are not asked twice; it comes back when you switch to Online (and is brought into line whenever you open the inbox). What to say when you are away is still the bubble's tooltip while you are away.
  • The Online/Offline switch copes with a sleeping chat server. Opening the inbox or the on-page console while the chat server is asleep used to leave the switch greyed out on … for minutes. It now keeps asking every few seconds while the server wakes (the line under the title reads Waking the chat server…), and if it still gets no answer after a couple of minutes the switch turns into Retry rather than sitting there disabled.
    • The site counts you as on if any agent is online. It learns this when someone flips the switch in Cactus, and re-checks whenever the Cactus inbox is opened. Flip availability in the Chatwoot mobile app instead and the site catches up next time somebody opens the Cactus inbox - use the Cactus switch if you want the bubble to follow at once.
    • The chat server used to print Typically replies in a few minutes on that screen whatever the switch said, because it was quietly reading a set of Monday-to-Friday office hours nobody had asked for. The module now clears those the first time the inbox is opened after an update, and the line reads honestly from then on.
  • The page journey (this visit only, with rough dwell times) buffers in sessionStorage and leaves the browser only when a chat is opened - and only at all under the same permission as the bubble.
  • The bubble can drop to its icon alone on phones. Settings → Live Chat → Widget → Hide the label on phones. On screens narrower than 640px the wording disappears and the bubble becomes a round chat icon, which keeps it off sticky basket bars, filter rows and anything else that lives in the bottom corner of a small screen. Desktop keeps the full pill. Off by default.
  • A chat button in the phone bottom bar. If your header or footer carries a Mobile Bar, one of the cells you can put in it is Live chat: message us - a message icon with your own caption under it. Pressing it opens the chat exactly as the bubble does, cookie question, invisible challenge and all, because it asks the widget to open rather than trying to start anything itself. The Live Chat block still has to be on the page for there to be a chat to open. The bubble carries on as normal alongside it - it is what starts the chat, after all - and lifts itself clear of the bar rather than sitting behind it. Pair the cell with Hide the label on phones and you get a tidy icon bubble above a tidy bar rather than two competing pills. Staff get the same button: pressing it opens their agent console rather than a customer chat, so a chat can still be answered on a phone with the console hidden.
  • An unread count on the bar's chat button. When a reply lands while the visitor has the chat shut, the message icon in the bottom bar picks up a number, in the same badge colours as the basket count. It comes from the chat's own tally of replies they have not seen, stays put as they move between pages, and clears the moment they open the chat to read it. For staff it shows the same number as their console button - chats waiting on an answer. It keeps counting as they browse: once a visitor has chatted, later pages that visit reconnect the chat quietly behind the bubble, without opening it, so a reply sent while they look around still arrives - as the badge, and as Chatwoot's own little preview of the message. Someone who has never opened the chat still loads nothing at all.
  • The bubble makes way for the slide-out basket. On a shop whose basket slides out over the page, the bubble used to sit on top of the panel's View full basket button. While the basket is open the bubble now disappears and the panel carries the chat instead: a full-width button under View full basket, in the bubble's colours and with its wording (Leave us a message when you are away). Pressing it shuts the basket and opens the chat, cookie question and all. Shut the basket and the bubble is back. Nothing to set up; with chat switched off there is no button, and a site with no shop never notices any of this.
  • The phone bar's chat button asks about cookies too. Pressing the Mobile Bar's chat cell (or the basket's chat button) before the live chat cookie has been allowed now shows the same Live chat needs a cookie notice the bubble does, rather than starting the chat regardless.
  • The bubble can go entirely on phones. Settings → Live Chat → Widget → Hide the bubble on phones. For a site whose bottom bar already carries a chat cell, the bubble is a second door to the same room sat over the page on the smallest screen there is - this shuts it, and the bar cell becomes the way in. It applies to staff too: the floating agent console hides on phones alongside it, and the bar cell opens that instead. Desktop keeps its bubble and its console. Off by default, and worth leaving off unless something else on the phone opens the chat, or there is no way in at all.
  • Logged-in members are identified with a server-computed HMAC, so nobody can impersonate someone else's email by fiddling with the page.
  • Retention: a nightly job deletes resolved conversations older than the configured months (default 12) on the Chatwoot server and in the mirror. Keep the number in step with the site's privacy policy.
  • When core Turnstile is configured, opening chat runs a managed (invisible for most people) challenge first.

Answering

With Unified Inbox installed, chats are answered there instead, alongside email, enquiries and phone calls. This module's own inbox tab stands down; the widget, the agent console on the site, the mobile app and everything else here are untouched. Replying from the hub still uses your own agent account, so if you have not connected yours yet it will say so rather than answering as somebody else.

  • Admin → Live Chat: two-pane inbox. Open/Resolved tabs, unread badges, customer-typing indicator (receive-only - customers never see staff typing), attachments, / inserts canned replies, resolve/reopen. Canned replies live on the chat server, so the mobile app shares them.
  • On the public site: staff with the livechat.view permission see a floating agent console instead of the customer widget - answer without leaving the page. It's styled to match the customer widget (brand header with the Online/Offline switch, avatars, chat bubbles), follows the site's dark mode, and while closed it still announces new messages within about half a minute - the button pulses red with an unread count. (It used to hold a live line open to the chat server for this, which kept the server awake and billed around the clock for as long as anyone had the shop open; the live line now opens with the console.) It clears the phone bottom bar rather than sitting behind it, and hides on phones when Hide the bubble on phones is on - the bar's chat cell opens it in that case.
  • Mobile: the official Chatwoot app against your chat server. Push notifications come from there.

Email from the chat server

The chat server sends its own emails (missed-message alerts to agents, transcripts, password resets), and being a separate machine it cannot read the site's email settings directly. Fill in the SMTP section under Settings → Email (for Brevo that is the SMTP key from SMTP & API → SMTP, not the ordinary API key), deploy so the values reach the site, then press Sync email to chat server on Settings → Live Chat - the module copies them across and restarts the chat server with email switched on. Until then chat works fully; it just sends no email, and the mobile app's push covers the alerting.

Backups and updates

  • The chat database is outside Cactus backups, so the module gives it its own: a nightly dump (the cron call wakes a suspended machine), shipped to S3-compatible storage with unguessable filenames, newest 30 kept - all surfaced on Settings → Backup next to the site backup, with a Back up now button and downloads via short-lived signed URLs.
  • Updates: the image repo watches upstream Chatwoot releases - security patches rebuild and apply automatically overnight; minor/major versions wait for the Update Chatwoot button (which takes a fresh dump first, then swaps the machine image; Chatwoot migrates itself on boot).
  • Knowing when to press it: the status card compares Cactus builds of the chat image, not just Chatwoot version numbers. Every build is tagged like v4.16.2-cactus.4, so a Cactus-only change to the chat (a tweak to how the widget looks, say) shows as update available even though Chatwoot itself has not moved. The button moves the machine to that exact build rather than to latest, so the card can always say which one is running. A server still on latest from before this is matched by what it actually pulled; if that cannot be worked out, the card says so and one press sorts it. A newer Chatwoot release that has not been built into the image yet gets its own line, so you know it is coming rather than wondering why the button does nothing new. The build list comes straight from the public image registry, checked at most every ten minutes, and costs no GitHub allowance.
  • The settings tab shows machine state, health, running version and last backup, and emails nothing - if the machine is unreachable the card says so in plain sight.

Contact-form integration

With the contact-form module installed, each submission's detail page gains a Live chats with this person panel (matched by email), the inbox toolbar gains a Live Chats button, and inside Chatwoot a Dashboard App panel shows the person's form history while you chat. Email is the join key throughout.

Permissions

Key Grants
livechat.view See the inbox, transcripts, and the frontend agent console
livechat.reply Send replies, resolve/reopen, save a personal agent token
livechat.manage Settings, canned reply management, machine wake/update, backups, provisioning

Clone this wiki locally