Loading...
 

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).

Screenshot From 2025 09 21 09 34 22

  • 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:
bash
sudo apt update sudo apt install prosody sudo systemctl enable --now prosody


Verify installation:

bash
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):

bash
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).

bash
# 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:

lua
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:

bash
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):

bash
sudo apt update sudo apt install python3-certbot-apache -y sudo certbot --apache -d tiki.example.org -d www.tiki.example.org


Verify:

bash
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.

lua
-- 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

lua
----------- 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.

lua
----------- 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:

lua
VirtualHost "xmpp.example.org" -- ...(rest of the block from above)... cloud_notify_push_service = "push.xmpp.example.org"


Validate and apply:

bash
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):

apache
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:

bash
sudo apache2ctl configtest && sudo systemctl reload apache2

5.2 Generate and install the shared secret

bash
# 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

lua
-- replace with the actual secret from /etc/prosody/xmpp_http_secret http_auth_credentials = "prosody:XXXXXYYYYYYZZZZ"

bash
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 prosody and 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

bash
# 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:

ConverseJS Extra Settings
{ "websocket_url": "wss://xmpp.example.org/xmpp-websocket", "discover_connection_methods": false, "keepalive": true, "persistent_store": "sessionStorage" }


Notes:

  • discover_connection_methods: false skips XEP-0156 discovery entirely — simplest option if you're setting websocket_url explicitly here anyway (see 6.1 if you'd rather rely on discovery).
  • persistent_store: localStorage keeps the session across browser restarts; sessionStorage avoids a harmless "no resumeable session" warning some users notice during testing/development. Pick localStorage for a smoother experience in production once things are working.
  • Add "debug": true temporarily 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:

/var/www/xmpp/.well-known/host-meta
<?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>

bash
sudo chown -R www-data:www-data /var/www/xmpp sudo chmod -R 755 /var/www/xmpp


Apache snippet (adjust to your vhost file):

apache
# 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

logs
# 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

bash
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 mode preference (-+xmpp_anonymous_mode+-):
    • Community room → she joins xmpp_anonymous_room alongside 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.

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):

Gajim — manual account settings
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
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:

wiki
{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


Failed

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:

server, as root
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


Certification Expired

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:

/etc/apache2/sites-available/blocked-subdomains.conf
<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>

bash
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 = false on 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 Authorization header instead of ending up in access logs.
  • SASL PLAIN warnings: keep c2s_require_encryption = true and 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 register for a host using HTTP auth.


smile 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.