How to install Prosody using Tiki for its userlist
Prosody is a lightweight and modular XMPP (Jabber) server.
This page describes how to set up Prosody so that it uses the Tiki database as its identity provider (IdP) for:
- Users
- Groups
- Authentication
It also shows how to expose anonymous chat for visitors (via Converse.js), and how to auto-join registered Tiki users to group chatrooms based on their Tiki group membership.
Note that this is a specialized setup, and it touches several systems at once (Prosody, Apache, Tiki, and optionally a firewall and mobile push). Budget real time for it, and follow the steps in order.
What you'll get: user stories
These are the scenarios this guide sets up. Keep them in mind while you configure things below — it helps to know *why* each piece exists.
- Alice (anonymous visitor): not registered. She sees the Converse.js chat pop up and asks a question.
- Bob (registered user): automatically added to the Registered group in Tiki, and to the associated Prosody chatroom. Every time he logs in, he sees the room's messages.
- Charlie (translator): also a member of the Translators group, so he's additionally auto-joined to the Translators chatroom.
- Dan (geek): already has his own external XMPP address and configures Tiki to use it instead of a Tiki-issued one — he still gets auto-joined to his group chatrooms.
- Eve (administrator / support): prefers a desktop client (Gajim) over the web interface, and is the one answering anonymous visitors like Alice.
Preparation / Assumptions
- A working instance of Tiki is available. This guide assumes Tiki stores its own users, groups and permissions in its database (default). It has not been tested with setups using LDAP, Shibboleth, etc.
- Prosody is installed on GNU/Linux (Debian/Ubuntu recommended).
- Domains used throughout this guide (replace with your own):
-
xmpp.example.org→ registered users (authenticated via Tiki) -
guest.example.org→ anonymous visitors -
conference.example.org→ MUC component (group chatrooms)
-
- DNS (A/AAAA + SRV) and Let's Encrypt certificates are set up for these hosts.
- An administrative account in Tiki is available.
1) Install Prosody
Obtain and install Prosody from your package manager or the official repository.
- Debian/Ubuntu:
sudo apt update sudo apt install prosody sudo systemctl enable --now prosody
Verify installation:
sudo prosodyctl check config sudo prosodyctl about
After any config edit, reload rather than restart when possible (see 11.2 for why a full restart is sometimes still needed, e.g. after a certificate renewal):
sudo systemctl reload prosody sudo systemctl status prosody
1.1 Install community modules (for HTTP auth)
Prosody's HTTP auth comes from the community module mod_auth_http (community modules repository).
# Common prerequisites (adjust to your distro): sudo apt install lua-sec mercurial -y # Clone community modules (place under /usr/local/lib/prosody) sudo mkdir -p /usr/local/lib/prosody cd /usr/local/lib/prosody sudo hg clone https://hg.prosody.im/prosody-modules/ modules # Alternative (git mirror): # sudo git clone https://github.com/bjc/prosody-modules /usr/local/lib/prosody/modules
In /etc/prosody/prosody.cfg.lua add:
plugin_paths = { "/usr/local/lib/prosody/modules" }
This is what makes authentication = "http" available further down.
2) TLS certificates
Using Let's Encrypt for production:
sudo apt install certbot -y sudo certbot certonly --standalone \ -d xmpp.example.org \ -d guest.example.org \ -d conference.example.org # Import certificates into Prosody's cert store sudo prosodyctl --root cert import /etc/letsencrypt/live
If your Tiki site is already running under Apache, you can instead request/renew the certificate through the Apache plugin (handy if you want one certbot flow for both the Tiki site and the XMPP subdomains):
sudo apt update sudo apt install python3-certbot-apache -y sudo certbot --apache -d tiki.example.org -d www.tiki.example.org
Verify:
sudo prosodyctl check certs
Expected output: a certificate found for each of xmpp.example.org, guest.example.org and conference.example.org.
More details: Prosody Certificate Documentation
Don't skip 11.2 below — Let's Encrypt certificates expire every 90 days, and Prosody needs an explicit nudge to pick up a renewed one.
3) Enable required modules
In /etc/prosody/prosody.cfg.lua, start with the global settings and the module list. The actual VirtualHost/Component blocks come in section 4 — keep them separate, it makes the file much easier to scan.
-- Prosody Configuration File plugin_paths = { "/usr/local/lib/prosody/modules" } admins = { "admin@xmpp.example.org" } modules_enabled = { -- Generally required "disco"; -- Service discovery "roster"; -- Allow users to have a roster "saslauth"; -- Authentication for clients and servers "tls"; -- Secure TLS on c2s/s2s connections -- Not essential, but recommended "blocklist"; -- Allow users to block communications with other users "bookmarks"; -- Synchronise the list of open rooms between clients "carbons"; -- Keep multiple online clients in sync "dialback"; -- Verify remote servers using DNS "limits"; -- Bandwidth limiting for XMPP connections "pep"; -- Public/private data storage per account "private"; -- Legacy account storage mechanism (XEP-0049) "smacks"; -- Stream management and resumption (XEP-0198) "vcard4"; -- User profiles (stored in PEP) "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar/vcard -- Nice to have "csi_simple"; -- Traffic optimizations for mobile devices "invites"; -- Create and manage invites "invites_adhoc"; -- Admins/users can create invitations via their client "invites_register";-- Allow invited users to create accounts "ping"; -- Replies to XMPP pings with pongs "time"; -- Report server time "uptime"; -- Report server uptime "version"; -- Replies to server version requests "mam"; -- Store recent messages (multi-device sync, history) -- Admin interfaces "admin_adhoc"; -- Administration via an XMPP client with ad-hoc commands "admin_shell"; -- Secure administration via 'prosodyctl shell' -- HTTP modules "bosh"; -- BOSH clients ("Jabber over HTTP") "websocket"; -- XMPP over WebSockets "http_file_share"; -- HTTP File Upload (XEP-0363) for images/videos/documents } modules_disabled = { -- "offline"; -- Store offline messages -- "c2s"; -- Handle client connections -- "s2s"; -- Handle server-to-server connections } pidfile = "/run/prosody/prosody.pid"; s2s_secure_auth = true limits = { c2s = { rate = "10kb/s"; }; s2sin = { rate = "30kb/s"; }; } -- Message archive (MAM) retention. "never" keeps everything; use a duration -- like "1w" or "6m" if you'd rather Prosody prune old history automatically. archive_expires_after = "never" log = { debug = "/var/log/prosody/prosody.debug"; info = "/var/log/prosody/prosody.log"; error = "/var/log/prosody/prosody.err"; { levels = { "error" }; to = "syslog"; }; } certificates = "/etc/prosody/certs" interfaces = { "0.0.0.0" } http_interfaces = { "0.0.0.0" } https_interfaces = { "0.0.0.0" } c2s_interfaces = { "0.0.0.0" } s2s_interfaces = { "0.0.0.0" } http_ports = { 5280 } https_ports = { 5281 } s2s_require_encryption = true consider_websocket_secure = true https_ssl = { key = "/etc/letsencrypt/live/xmpp.example.org/privkey.pem"; certificate = "/etc/letsencrypt/live/xmpp.example.org/fullchain.pem"; } Include "conf.d/*.cfg.lua"
Note: archive_expires_after is easy to miss and easy to regret — a short value here silently deletes old chat history. If in doubt, set it to "never" and manage disk usage separately.
4) Define VirtualHosts and Components
Prosody needs one explicit block per role. Append these to the same prosody.cfg.lua file, after the section above.
| Role | Domain | Purpose | |
| Registered users | xmpp.example.org | Tiki HTTP authentication | |
| Anonymous visitors | guest.example.org | Converse.js anonymous chat (SASL ANONYMOUS) | |
| Group chat | conference.example.org | MUC rooms, with history | |
| Push notifications (optional) | push.xmpp.example.org | XEP-0357, only needed for mobile push | |
----------- VirtualHost: Registered Users (Tiki HTTP Auth) ----------- VirtualHost "xmpp.example.org" ssl = { key = "/etc/letsencrypt/live/xmpp.example.org/privkey.pem"; certificate = "/etc/letsencrypt/live/xmpp.example.org/fullchain.pem"; } authentication = "http" http_auth_url = "https://tiki.example.org/tiki-xmpp-auth.php" http_auth_credentials = "prosody:REPLACE_WITH_YOUR_SECRET_HERE" http_auth_user = "prosody" c2s_require_encryption = true allow_unencrypted_plain_auth = false sasl_mech_list = { "PLAIN" } disco_items = { { "conference.example.org" } } ----------- VirtualHost: Anonymous Visitors ----------- VirtualHost "guest.example.org" authentication = "anonymous" ssl = { key = "/etc/letsencrypt/live/xmpp.example.org/privkey.pem"; certificate = "/etc/letsencrypt/live/xmpp.example.org/fullchain.pem"; } c2s_require_encryption = true allow_registration = false ----------- MUC Component ----------- Component "conference.example.org" "muc" ssl = { key = "/etc/letsencrypt/live/xmpp.example.org/privkey.pem"; certificate = "/etc/letsencrypt/live/xmpp.example.org/fullchain.pem"; } restrict_room_creation = false modules_enabled = { "muc_mam" } muc_room_default_persistent = true muc_room_default_public = true muc_room_default_members_only = false muc_room_default_allow_anonymous = true muc_room_default_history_length = 20 muc_max_history_messages = 100 muc_tombstones = true -- Lets anonymous visitors get a stable auto-generated nickname instead -- of being prompted, useful if you want guest chat to be frictionless: -- muc_nickname_from_jid = true
Note: muc and muc_mam are not in the global modules_enabled list — they only make sense on the MUC Component, so they're declared in its own modules_enabled.
4.1 Optional: push notifications for mobile clients (XEP-0357)
Skip this entirely unless you actually need mobile push (e.g. for a client like Monal on iOS). It is not required for the web chat (Converse.js) or for desktop clients.
----------- Push Component ----------- -- Add "push" and "cloud_notify" to the global modules_enabled list (section 3) first. Component "push.xmpp.example.org" "cloud_notify" -- Example for the Monal iOS client: Monal uses its own Apple Push -- certificate server-side, and this exact app_id is what makes that work. -- If you're not targeting Monal specifically, check your client's docs -- for the app_id it expects. app_id = "im.monal.monal"
And on the registered-users VirtualHost, point it at the push component:
VirtualHost "xmpp.example.org" -- ...(rest of the block from above)... cloud_notify_push_service = "push.xmpp.example.org"
Validate and apply:
sudo prosodyctl check config sudo systemctl restart prosody sudo journalctl -u prosody -f | grep -i push # Expected: component 'push.xmpp.example.org' loaded, no module load errors
5) Tiki HTTP auth endpoint
Tiki provides tiki-xmpp-auth.php, which answers /check_password and /user_exists for Prosody's mod_auth_http.
5.1 Web server requirements (Apache)
Apache must forward the Authorization header to PHP — this is essential for HTTP Basic auth to reach the script. Add these lines inside the <VirtualHost> (or the <Directory> serving Tiki):
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 CGIPassAuth On <Directory /var/www/tiki> Options -Indexes +IncludesNOEXEC +SymLinksIfOwnerMatch Require all granted SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 CGIPassAuth On </Directory>
Reload Apache:
sudo apache2ctl configtest && sudo systemctl reload apache2
5.2 Generate and install the shared secret
# Generate a secret and store it where both Prosody and Tiki can use it sudo sh -c 'head -c32 /dev/urandom | base64 | tr -d /=+ | cut -c1-40 > /etc/prosody/xmpp_http_secret' sudo chown root:prosody /etc/prosody/xmpp_http_secret sudo chmod 640 /etc/prosody/xmpp_http_secret # Show it (as root) to copy into Prosody's http_auth_credentials / Tiki's xmpp_shared_secret: sudo cat /etc/prosody/xmpp_http_secret
Important: do not expose this secret in public logs or commit it to a repository.
5.3 Point Prosody at the secret
-- replace with the actual secret from /etc/prosody/xmpp_http_secret http_auth_credentials = "prosody:XXXXXYYYYYYZZZZ"
sudo systemctl restart prosody
5.4 Configure Tiki
Set the Tiki preference XMPP shared secret (HTTP auth) (-+xmpp_shared_secret+-) to the same value as /etc/prosody/xmpp_http_secret.
The tiki-xmpp-auth.php script accepts either:
- HTTP Basic Auth (-+Authorization: Basic ...+-) with username
prosodyand the shared secret as password (preferred), or - a
secret=...query/POST parameter (avoid in production — it can end up logged in URLs).
5.5 Quick diagnostics
# Endpoint reachable? (without auth you should get a 403, not a connection error) curl -I "https://tiki.example.org/tiki-xmpp-auth.php" # With Basic auth (preferred): SECRET=$(sudo cat /etc/prosody/xmpp_http_secret) curl -v -u "prosody:${SECRET}" \ "https://tiki.example.org/tiki-xmpp-auth.php/check_password?user=admin&pass=THEPASSWORD" # With ?secret= (fallback, avoid in production): curl -v "https://tiki.example.org/tiki-xmpp-auth.php/check_password?user=admin&pass=THEPASSWORD&secret=${SECRET}"
Expected output for valid credentials: true (plain text) from /check_password.
If you get 403 or false, check, in order:
- Apache is forwarding
Authorization(5.1) — this is the #1 cause. - Prosody is actually sending Basic auth (check the Prosody log for the outgoing request).
- The secret matches exactly on both sides.
6) Configure Converse.js (in Tiki)
Under Admin → XMPP → ConverseJS Extra Settings, a minimal, recommended starting point:
{ "websocket_url": "wss://xmpp.example.org/xmpp-websocket", "discover_connection_methods": false, "keepalive": true, "persistent_store": "sessionStorage" }
Notes:
-
discover_connection_methods: falseskips XEP-0156 discovery entirely — simplest option if you're settingwebsocket_urlexplicitly here anyway (see 6.1 if you'd rather rely on discovery). -
persistent_store:localStoragekeeps the session across browser restarts;sessionStorageavoids a harmless "no resumeable session" warning some users notice during testing/development. PicklocalStoragefor a smoother experience in production once things are working. - Add
"debug": truetemporarily while troubleshooting, and remove it afterwards — it's noisy.
6.1 Optional: XEP-0156 discovery
If you'd rather let Converse.js discover the BOSH/WebSocket endpoints instead of hardcoding websocket_url above, publish a host-meta file:
<?xml version='1.0' encoding='utf-8'?> <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'> <link rel="urn:xmpp:alt-connections:xbosh" href="https://xmpp.example.org:5281/http-bind" /> <link rel="urn:xmpp:alt-connections:websocket" href="wss://xmpp.example.org:5281/xmpp-websocket" /> </XRD>
sudo chown -R www-data:www-data /var/www/xmpp sudo chmod -R 755 /var/www/xmpp
Apache snippet (adjust to your vhost file):
# Prevent a reverse proxy from swallowing .well-known ProxyPass /.well-known ! Alias /.well-known/ /var/www/xmpp/.well-known/ <Directory /var/www/xmpp/.well-known> Options -Indexes AllowOverride None Require all granted Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET, OPTIONS" Header set Access-Control-Allow-Headers "Content-Type" </Directory>
6.2 Useful logs while troubleshooting
# Apache access log sudo tail -f /var/log/apache2/example.org_access_log # Prosody log sudo tail -f /var/log/prosody/prosody.log # Prosody service log sudo journalctl -u prosody -f # Apache error log sudo tail -f /var/log/apache2/example.org_error_log
7) Test the endpoints
curl -vk https://xmpp.example.org:5281/http-bind curl -vk https://xmpp.example.org:5281/xmpp-websocket
Expected:
- BOSH: an HTML page saying "Prosody BOSH endpoint – It works"
- WebSocket:
HTTP/1.1 101 Switching Protocols
8) How rooms work, and using a desktop client (Gajim)
8.1 Room creation model
Rooms aren't created manually in advance. A room is created automatically the first time someone (or Tiki, on their behalf) joins it, based on:
- the room JID
- the configured MUC domain
- the auto-join logic driven by Tiki (see
xmpp_auto_join_strategy)
Depending on xmpp_auto_join_strategy, Tiki can also proactively create/configure a room and set a user's affiliation in it as soon as their Tiki group membership changes, rather than waiting for someone to join by hand.
Example rooms for the user stories at the top of this page:
- Community room (anonymous + registered users):
community@conference.example.org - Support room (anonymous support):
support@conference.example.org - Registered users room:
registered@conference.example.org - Translators room:
translators@conference.example.org
8.2 Registered users (Bob, Charlie, Dan)
When a user logs into Tiki, they're authenticated to XMPP via HTTP auth and auto-joined to rooms based on their Tiki groups (per xmpp_group_room_map):
- Bob (group: Registered) → auto-joined to
registered@conference.example.org - Charlie (group: Translators) → auto-joined to
translators@conference.example.org(in addition to Registered) - Dan (external XMPP account set in his Tiki profile) → joins the same group-mapped rooms, using his own JID instead of a Tiki-issued one
8.3 Anonymous visitors (Alice)
- Alice visits the Tiki site; Converse.js opens automatically.
- Where she lands depends on the
Anonymous visitor modepreference (-+xmpp_anonymous_mode+-):- Community room → she joins
xmpp_anonymous_roomalongside other visitors and registered members. - Personalized support → she joins
xmpp_anonymous_support_room, a room where support staff (like Eve) are expected to be present.
- Community room → she joins
8.4 Admin / support workflow with Gajim (Eve)
Eve prefers a desktop client. Add her account (which, for support duty, should be the same account listed as the XMPP admin in Tiki):
JID: admin@xmpp.example.org Password: this account's Tiki password Server (override): Host: xmpp.example.org Port: 5222 Encryption: StartTLS (Require TLS) Auth method / SASL: PLAIN Verify server certificate: ON (unless testing with a self-signed cert) Notes: - If DNS SRV records are correct, you can skip the Host override. - Only use SASL PLAIN over an encrypted (TLS) connection.
To join or create a room, Eve just joins it — Prosody creates it if it doesn't exist yet:
Gajim → + → Join Group Chat Room: support Server: conference.example.org
This joins (creating it if needed) support@conference.example.org. Eve stays online there to answer anonymous visitors in real time — messages Alice sends via Converse.js on the website show up directly in Gajim.
9) Configure Tiki (Admin → XMPP)
| Section | Preference | Example value | |
| Common (Openfire & Prosody) | XMPP MUC Domain | conference.example.org
| |
| XMPP BOSH URL (http-bind) | https://xmpp.example.org:5281/http-bind/
| ||
| XMPP WebSocket URL | wss://xmpp.example.org:5281/xmpp-websocket
| ||
| Prosody (HTTP Auth) | XMPP domain for registered users | xmpp.example.org
| |
| XMPP domain for anonymous visitors | guest.example.org
| ||
| Anonymous visitor mode | Community room or Personalized support | ||
| Default anonymous chat room | community@conference.example.org
| ||
| Anonymous support room | support@conference.example.org
| ||
| Default registered chat room | registered@conference.example.org
| ||
| Mapping Tiki groups to chat rooms | { "Registered": "registered@conference.example.org", "Translators": "translators@conference.example.org" }
| ||
| Auto-join strategy | By groups (recommended) | ||
| XMPP shared secret (HTTP auth) | (same value as /etc/prosody/xmpp_http_secret)
| ||
| CORS allowed origins (comma-separated) | https://tiki.example.org
| ||
| ConverseJS options (common) | Always Load ConverseJS | Enable if you want the chat widget on every page | |
| ConverseJS Debug Mode | Enable only while debugging | ||
| ConverseJS Extra Settings | see section 6 | ||
Then, on a wiki page, add the XMPP plugin for whichever room you want to expose there:
{xmpp room="Support"} # or {xmpp room="Registered"} # or, scoped to a group: {GROUP(groups="Translators")} {xmpp room="Translators"} {GROUP}
10) Pitfalls & troubleshooting
10.1 Firewall (firewalld / nftables) — the #1 real-world blocker
This is the most common cause of "No route to host" when Prosody is running correctly on the server. From a remote machine:
nc -vz xmpp.example.org 5222
Result: nc: connect to xmpp.example.org port 5222 failed: No route to host — even though, on the server, ss -lntp | grep 5222 shows LISTEN 0.0.0.0:5222.
Detect firewalld:
systemctl status firewalld
If it says active (running), firewalld is active — even if ufw isn't installed. Confirm with nft list ruleset (a table inet firewalld { ... } entry means it's enforcing rules via nftables).
By default, firewalld's public zone typically allows 22, 80/443, and mail ports, but not 5222 (XMPP client), 5269 (server-to-server), or 5280/5281 (BOSH/WebSocket).
Fix — open the XMPP ports:
firewall-cmd --permanent --add-port=5222/tcp firewall-cmd --permanent --add-port=5269/tcp firewall-cmd --permanent --add-port=5280/tcp firewall-cmd --permanent --add-port=5281/tcp firewall-cmd --reload
Verify: firewall-cmd --list-ports should list all four.
Confirm from another machine:
nc -vz xmpp.example.org 5222
Expected: Connection to xmpp.example.org 5222 port tcp/xmpp-client succeeded!
Only configure Gajim or Converse.js after this succeeds.
10.2 SSL certificate expired but still served by Prosody
Symptom: Gajim reports "The certificate has expired". Opening its certificate details may even crash Gajim with AttributeError: 'NoneType' object has no attribute 'connect'.
Cause: Let's Encrypt certificates expire every 90 days. A new one was issued on disk, but Prosody kept serving the old one it had already loaded into memory.
Diagnosis:
# 1. Certificate on disk sudo openssl x509 -in /etc/letsencrypt/live/xmpp.example.org/fullchain.pem -noout -dates # 2. Renewal sanity check sudo certbot renew # 3. What the server is actually presenting echo | openssl s_client -connect xmpp.example.org:5222 -starttls xmpp -servername xmpp.example.org 2>/dev/null | openssl x509 -noout -dates # If the disk cert is valid but the server sends an expired one -> Prosody needs a nudge. # If both are expired -> renew first, then follow the fix below.
Fix: force Prosody to reload the certificate (a plain reload isn't always enough here — a restart is the reliable option):
sudo systemctl restart prosody
Prevention: add a certbot deploy hook that re-imports certificates into Prosody automatically after every renewal (the method recommended by Prosody's own docs: https://prosody.im/doc/letsencrypt):
sudo nano /etc/letsencrypt/renewal-hooks/deploy/prosody.sh
#!/bin/sh /usr/bin/prosodyctl --root cert import /etc/letsencrypt/live
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/prosody.sh
10.3 Keep the XMPP subdomains out of search engines
The xmpp.* and guest.* subdomains serve no browsable content and shouldn't be indexed. Block them at the Apache level:
<VirtualHost *:80> ServerName xmpp.example.org ServerAlias guest.example.org DocumentRoot /var/www/empty <Directory /var/www/empty> Require all granted </Directory> Header set X-Robots-Tag "noindex, nofollow" </VirtualHost>
sudo mkdir -p /var/www/empty sudo chown -R www-data:www-data /var/www/empty sudo a2enmod headers sudo a2ensite blocked-subdomains.conf sudo systemctl reload apache2
Test by opening http://xmpp.example.org and http://guest.example.org directly: you should get an empty/default page, no Tiki content.
10.4 Other common issues
- Certificate mismatch: make sure every VirtualHost/Component points to a valid cert pair for its own hostname.
- Room creation restricted: set
restrict_room_creation = falseon the MUC Component (or handle room creation via an admin account only, if that's the policy you want). - Duplicate anonymous nicknames on reload: expected behavior; let Converse.js generate a unique nickname rather than fighting it.
- Apache drops the Authorization header: see 5.1 (-+SetEnvIf Authorization+- +
CGIPassAuth On). - Don't pass the shared secret in a URL in production: prefer Basic auth (5.4) so it travels in the
Authorizationheader instead of ending up in access logs. - SASL PLAIN warnings: keep
c2s_require_encryption = trueand connect over TLS/HTTPS (BOSH or WebSocket) rather than disabling encryption to make the warning go away. - Converse "no resumeable session": benign; clear the browser storage or use
persistent_store: "sessionStorage"(6). - XMPP admin shows as unauthorized in the logs: make sure the XMPP Admin JID in Tiki maps to a real Tiki user whose password matches — don't rely on
prosodyctl registerfor a host using HTTP auth.
At this point:
- Converse.js can connect via BOSH or WebSocket.
- Anonymous visitors (-+guest.example.org+-) can chat.
- Registered users (-+xmpp.example.org+-) can join their group rooms automatically.
- Group chats are served by
conference.example.org, with message history.