XMPP Fundamentals

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

The Chatterbox chat application of this section is built several times: on a custom WebSocket protocol (see Message Design and Serialization), and, on the pages of this XMPP group, on a standard protocol that already defines presence, rooms, history, receipts, corrections and reactions. This page is the foundation of the group. It explains how XMPP is organised, what a stanza looks like on the wire, how a client logs in and finds out what a server can do, how the standards process produces the extensions that the later pages use, and how to decide whether XMPP or a custom protocol fits a product.

The site already has a compact introduction to JIDs and stanzas written for chatbots in XMPP Bots, and a comparison of messaging protocols in Messaging Protocols. This page does not repeat them. It goes deeper on the parts a browser chat developer needs: the stream and authentication lifecycle, the roster, discovery, and the choice between XMPP and a hand-made protocol.

Versions and specification statuses were checked on 2026-10-24 (the exploration baseline was fixed on 2026-10-22): RFC 6120, RFC 6121, RFC 7622 and RFC 7395 are Proposed Standards; the newest XMPP Compliance Suites document is XEP-0479 (2023 edition, status Experimental); the servers used on these pages are ejabberd 26.09, Prosody 13.0.7, Openfire 5.1.2 and MongooseIM 6.10.0; the browser libraries are Strophe.js 5.0.0, @xmpp/client 0.14.0 and Converse.js 14.0.1.

Architecture: clients, servers, federation and components

XMPP (the Extensible Messaging and Presence Protocol) is a protocol for streaming XML between two network endpoints. The core, defined in RFC 6120, is deliberately small: it opens an XML stream, secures it with TLS, authenticates it with SASL, binds a resource and then carries three kinds of XML elements, called stanzas. Everything else, from group chat to file upload, is an extension (an XEP) that adds child elements to those three stanzas or new iq queries.

The architecture has three kinds of participant:

  • Clients connect to exactly one server, the one that hosts their account. A browser client connects with WebSocket (RFC 7395) or BOSH; native clients use TCP on port 5222 (STARTTLS) or 5223 (direct TLS).

  • Servers hold accounts, store the roster and offline or archived messages, and route stanzas. A server that receives a stanza for another domain opens a server-to-server (S2S) stream to that domain, found with DNS SRV records or the host-meta file, and delivers it there. This is federation: it works like e-mail, so users of chat.example.test can talk to users of any other domain without sharing a database. Federation can be switched off for a private deployment, and it has its own abuse problems (see the security page of this section).

  • Components are services that attach to a server under their own domain, for example conference.chat.example.test for multi-user chat or upload.chat.example.test for file upload. Modern servers run most components internally, but a standalone component can connect through the Jabber Component Protocol (XEP-0114).

XMPP federation: browser and phone clients of alice@chat.example.test connect to server A, bob@team.example.org connects to server B, the servers exchange stanzas over server-to-server streams, and a chat component attaches to server A

Because the server holds the state (the roster, presence of all resources, the archive), a client can be thin, and several clients of the same account (resources) can be online at once. Chatterbox’s browser client, a phone client and a desktop client can share one account; the server fans stanzas out to the right resource.

JIDs

Every entity has a Jabber ID (JID), defined in RFC 7622 (which replaced RFC 6122):

localpart@domainpart/resourcepart
alice@chat.example.test/web-3f9a
  • The domainpart is the server or service, and is the only mandatory part. chat.example.test is a server and conference.chat.example.test is a component.

  • The localpart identifies an account or, on a MUC service, a room (general@conference.chat.example.test).

  • The resourcepart identifies one connected client (web-3f9a) or, in a room, a nickname.

Each part is at most 1023 bytes in UTF-8. The localpart and resourcepart go through the PRECIS profiles of RFC 8265 (case-folded for the localpart, case-preserved for the resource), so Alice@chat.example.test and alice@chat.example.test are the same account. A bare JID (alice@chat.example.test) addresses the account: the server picks the right resources. A full JID (alice@chat.example.test/web-3f9a) addresses one connection. Treat the bare JID as the stable user identifier in Chatterbox (it plays the role of userId in the custom protocol) and never use a full JID as an account key.

JID characters that the grammar forbids, such as @ or a space, must be escaped in a localpart with the rules of XEP-0106 when a legacy identifier is mapped to a JID (for example an e-mail address becoming alice\40example.org).

XML streams over WebSocket

A native XMPP connection is one long XML document per direction: the client sends <stream:stream> and the server answers with its own, and the stanzas are children of those roots. XML parsers do not work well on a document that never ends, and a browser WebSocket delivers discrete messages anyway. RFC 7395 therefore changes the framing for WebSocket:

  • The WebSocket subprotocol token is xmpp.

  • Each WebSocket text message carries exactly one complete XML element: an <open/> or <close/> element in the urn:ietf:params:xml:ns:xmpp-framing namespace, or one stanza or stream-level element.

  • The stream is restarted (after TLS or SASL) by sending a new <open/>, with no new connection.

The following capture was taken from a local ejabberd 26.09 container by a small Node.js script using the ws package; it is the entire login of a client up to resource binding (the base64 payload is the SASL PLAIN string for the test account, a placeholder password on a loopback-only server, and the long lists were trimmed):

>> <open xmlns="urn:ietf:params:xml:ns:xmpp-framing" to="chat.example.test" version="1.0"/>
<< <open xmlns='urn:ietf:params:xml:ns:xmpp-framing' id='14624112876730713946' version='1.0' from='chat.example.test'/>
<< <stream:features xmlns:stream='http://etherx.jabber.org/streams'>
     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
       <mechanism>PLAIN</mechanism><mechanism>SCRAM-SHA-512</mechanism>
       <mechanism>SCRAM-SHA-256</mechanism><mechanism>SCRAM-SHA-1</mechanism>
       <mechanism>X-OAUTH2</mechanism>
     </mechanisms></stream:features>
>> <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGFsaWNlAGNoYW5nZS1tZS1hbGljZQ==</auth>
<< <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
>> <open xmlns="urn:ietf:params:xml:ns:xmpp-framing" to="chat.example.test" version="1.0"/>
<< <open xmlns='urn:ietf:params:xml:ns:xmpp-framing' id='10523234531799669867' version='1.0' from='chat.example.test'/>
<< <stream:features xmlns:stream='http://etherx.jabber.org/streams'>
     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> <sm xmlns='urn:xmpp:sm:3'/>
   </stream:features>
>> <iq xmlns="jabber:client" type="set" id="b1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>web</resource></bind></iq>
<< <iq xmlns='jabber:client' type='result' id='b1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>alice@chat.example.test/web</jid></bind></iq>

Source: RFC 7395: An XMPP Subprotocol for WebSocket and RFC 6120: XMPP Core. The plain ws:// URL above is acceptable only on a loopback development server; the server offers PLAIN there because the connection is not encrypted. Over wss:// use a SCRAM mechanism, which never sends the password. The browser-side details are on XMPP in the Browser.

The three stanzas

All application traffic is carried by three stanzas. They share the addressing attributes to and from, a type and an id, and carry their payload as child elements in their own namespaces.

message

A message is fire-and-forget: there is no acknowledgement in the core protocol. Its type is chat (one-to-one), groupchat (room), normal, headline or error.

<message type="chat" to="bob@chat.example.test" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA">
  <body>Hello, Chatterbox</body>
  <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

The body is the human-readable text; extensions add siblings (chat states, receipts, a reply reference). A client that does not understand a sibling ignores it, which is how XMPP stays extensible without breaking old clients. The message id plays the same role as the Chatterbox envelope id: it is chosen by the sender and is the target of corrections, receipts and reactions. See XMPP Chat Features.

presence

A presence stanza announces availability and is broadcast: a client sends one presence to its server and the server sends it to every contact subscribed to the account. A presence with a to attribute is directed (this is how a client enters a room). The optional show child is one of away, chat, dnd or xa; status is free text; priority ranks the resources. type="unavailable" announces going offline and the server sends it on the client’s behalf when a connection dies.

<presence>
  <show>away</show>
  <status>Back in ten minutes</status>
  <priority>0</priority>
</presence>

Presence is also the carrier of the subscription state machine described in the next section, using the types subscribe, subscribed, unsubscribe and unsubscribed.

iq

An iq (info/query) stanza is a request and response pair, and the only stanza that must always be answered. Its type is get or set for the request and result or error for the answer, and the id pairs them. Exactly one child element, in some extension namespace, carries the query. Resource binding above, the roster fetch below, service discovery and archive queries are all iq stanzas.

<iq type="get" id="roster-1">
  <query xmlns="jabber:iq:roster"/>
</iq>

An iq of type get or set that the recipient does not understand must be answered with an iq of type error and the condition service-unavailable or feature-not-implemented. Clients should apply a timeout to their own requests, because an unanswered iq is the only application-level acknowledgement the protocol has.

All three stanzas can answer with an error: the same stanza type is returned with type="error", the original payload may be included, and an error child with a defined condition (item-not-found, forbidden, not-authorized, bad-request, policy-violation and so on) explains the failure. These conditions play the role of the Chatterbox error codes.

Login lifecycle

The lifecycle of a connection is the same for every XMPP client, native or browser:

sequenceDiagram autonumber participant C as Browser client participant S as XMPP server C->>S: open stream (to chat.example.test) S-->>C: open + features (SASL mechanisms) C->>S: auth SCRAM-SHA-256 S-->>C: success C->>S: open stream again S-->>C: features (bind, Stream Management) C->>S: iq set bind (resource web) S-->>C: iq result (full JID) C->>S: iq get roster S-->>C: iq result (contacts) C->>S: presence (initial) S-->>C: presence of online contacts C->>S: message (type chat, to bob) S-->>C: receipt or reply from bob

The steps in order:

  1. Stream open and features. The server advertises what it supports (SASL mechanisms, then after authentication the bind feature and optional features such as Stream Management).

  2. Authentication with SASL (below). The stream restarts on success.

  3. Resource binding. The client asks the server to bind a resource, or lets the server choose, and learns its full JID.

  4. Roster fetch. The client downloads its contact list.

  5. Initial presence. Until the client sends its first presence, it is not "available" and receives no presence and, on most servers, no live messages for its resource. This is the single most common cause of "my client logs in but gets nothing" on a fresh implementation.

  6. Application traffic.

The roster and presence subscriptions

The roster is the server-side contact list of an account (RFC 6121). Each item has a jid, an optional display name, optional group elements and a subscription state:

<iq type="result" id="roster-1" to="alice@chat.example.test/web">
  <query xmlns="jabber:iq:roster" ver="ver42">
    <item jid="bob@chat.example.test" name="Bob" subscription="both"><group>Team</group></item>
    <item jid="carol@team.example.org" subscription="to"/>
  </query>
</iq>

Presence is subscription based. Alice sees Bob’s presence only when she has a subscription to Bob, and Bob has to approve it:

Roster subscription value Meaning

none

Neither side receives the other’s presence.

to

The account receives the contact’s presence (it subscribed, the contact approved).

from

The contact receives the account’s presence.

both

Presence flows both ways.

The handshake is four presence types: Alice sends <presence type="subscribe" to="bob@chat.example.test"/>, Bob’s server delivers it, Bob’s client answers type="subscribed", and the subscription moves to to (Alice) and from (Bob); Bob usually replies with his own subscribe to make it both. unsubscribe and unsubscribed revoke it. Roster versioning (the ver attribute) lets a client with a cached roster fetch only the changes, which matters for a web client that reconnects often. A chat application whose rooms are all public does not need the roster at all: room presence is handled by the room (see the multi-user chat section of the chat features page).

Authentication

XMPP authentication is SASL (RFC 4422) negotiated in the stream features. The mechanisms to know:

  • PLAIN sends the password in the clear inside the secured stream. It is acceptable only inside TLS, and the only way to use a password hash stored in a legacy backend.

  • SCRAM-SHA-1, SCRAM-SHA-256 and SCRAM-SHA-512 (RFC 5802 and RFC 7677) prove knowledge of the password without sending it, store only a salted verifier on the server, and have -PLUS variants that bind the exchange to the TLS channel (RFC 9266) so a man in the middle cannot relay it. Prefer SCRAM-SHA-256 or SCRAM-SHA-512, with -PLUS where the TLS library exposes channel binding (a browser WebSocket does not, so a browser client uses the plain SCRAM variants).

  • OAUTHBEARER and X-OAUTH2 carry an access token instead of a password, which is how a browser can log in without ever holding the user’s password. See XMPP in the Browser for the token pattern.

  • EXTERNAL authenticates with a TLS client certificate or an already-established identity; ANONYMOUS gives a guest account.

Three newer specifications reduce round trips and avoid storing passwords, and are part of the 2023 Compliance Suites where noted below:

  • SASL2 (XEP-0388, Draft) replaces the stream-restart sequence with one authenticate element and lets the client attach inline features to it, so a server can authenticate, bind and enable Stream Management in a single exchange.

  • Bind 2 (XEP-0386, Draft) is one of those inline features: it binds a resource and, in the same exchange, enables Carbons and Stream Management.

  • FAST (XEP-0484, status Proposed) lets the server issue a token at login that the client stores and presents next time instead of the password, which suits a web page that keeps a token in storage.

Not every server and library supports SASL2 yet. Check the XEPs it advertises in the stream features before relying on them, and fall back to the classic SASL plus bind sequence otherwise.

Reconnecting: Stream Management

A WebSocket that drops mid-conversation loses stanzas that were in flight. Stream Management (XEP-0198, Draft) adds a counter on each side: after enable with resume="true", each side counts the stanzas it handled and acknowledges them with <a h="N"/>. If the connection dies, the client reconnects, authenticates and sends <resume previd="s-42" h="7"/>; the server restores the session (resources, presence, room membership) and resends whatever the client missed, with no visible leave and rejoin. The server keeps the session for a bounded time (ejabberd’s resume_timeout, 300 seconds in the test configuration). This is the XMPP counterpart of Chatterbox’s hello resume block, covered by Connection Lifecycle, Heartbeats and Reconnection.

Service discovery and capabilities

A client never has to guess what a server supports. Service Discovery (XEP-0030, Final) defines two iq queries:

<iq type="get" to="chat.example.test" id="d1">
  <query xmlns="http://jabber.org/protocol/disco#info"/>
</iq>
<iq type="get" to="chat.example.test" id="d2">
  <query xmlns="http://jabber.org/protocol/disco#items"/>
</iq>

disco#info returns the identities and feature namespaces of an entity; disco#items lists its child entities. Asking a local ejabberd for the items of its domain returned three components: conference.chat.example.test (multi-user chat), pubsub.chat.example.test and upload.chat.example.test (HTTP File Upload). Asking its disco#info returned, among others, urn:xmpp:mam:2 and urn:xmpp:carbons:2. A client checks for a namespace before it offers the feature, which is how a user interface adapts to different servers.

Entity Capabilities (XEP-0115, Draft) avoids repeating those queries for every contact: a client puts a hash of its feature list in each presence, and receivers cache the answer by hash. This is also how a client learns that a contact’s client supports reactions or end-to-end encryption.

Standards and the XEP process

The XMPP Standards Foundation publishes extensions as XMPP Extension Protocols (XEPs). A XEP moves through the following statuses, and the status tells you how stable an implementation can expect the wire format to be:

Status Meaning

Experimental

Under discussion, may change incompatibly. Implementations exist, but treat the format as a moving target.

Proposed

Accepted for the standards track by the council, awaiting implementation experience.

Draft

Stable; two interoperable implementations exist. Changes are limited to clarifications.

Final

Complete and field-proven.

Deferred

No longer progressing (inactive), kept for reference.

Obsolete / Retracted

Replaced or withdrawn; do not implement.

The XEPs that Chatterbox uses, with the status published on 2026-10-24:

XEP Title Status Page

XEP-0030

Service Discovery

Final

this page

XEP-0115

Entity Capabilities

Draft

this page

XEP-0198

Stream Management

Draft

this page

XEP-0388 / XEP-0386

SASL2 / Bind 2

Draft

this page

XEP-0484

FAST (token authentication)

Proposed

this page

XEP-0156

Discovering Alternative XMPP Connection Methods

Draft

browser

XEP-0045

Multi-User Chat

Draft

chat features

XEP-0313

Message Archive Management

Draft

chat features

XEP-0280

Message Carbons

Draft

chat features

XEP-0085

Chat State Notifications

Final

chat features

XEP-0184

Message Delivery Receipts

Draft

chat features

XEP-0333

Displayed Markers

Draft

chat features

XEP-0308

Last Message Correction

Draft

chat features

XEP-0424

Message Retraction

Proposed

chat features

XEP-0444

Message Reactions

Experimental

chat features

XEP-0461

Message Replies

Experimental

chat features

XEP-0363

HTTP File Upload

Draft

chat features

XEP-0384

OMEMO Encryption

Experimental

OMEMO

XEP-0166 / XEP-0167

Jingle / Jingle RTP Sessions

Draft

Jingle

Source: XMPP Extensions index and the XEP list at xeplist.xml (statuses and versions copied on 2026-10-24).

Compliance Suites

With hundreds of XEPs, nobody can tell from a product page whether two clients and a server will work together. The XMPP Compliance Suites (XEP-0479 for the 2023 edition, the newest published one, at status Experimental and unchanged from the 2022 edition) group the XEPs by category and level:

  • Core and Web: RFC 6120, TLS (RFC 7590), service discovery, Entity Capabilities, and for Web the connection mechanisms RFC 7395 (WebSocket) or XEP-0206 (XMPP over BOSH), plus XEP-0156 for clients.

  • IM: RFC 6121 (roster and presence), Message Carbons, User Avatar, the blocking command, the MUC and MAM families, HTTP File Upload, and so on.

  • Mobile: Stream Management, Client State Indication and Push Notifications.

  • A/V Calling: Jingle audio and video with ICE-UDP and DTLS-SRTP transports, and External Service Discovery for TURN.

Each category has a Server, Client, Advanced Server and Advanced Client level. When you pick a server or a client library for Chatterbox, compare the suites they claim: a server that is IM and Web compliant at the Advanced level supports everything on the chat features page. See the XMPP servers page, XMPP Servers, for the servers compared.

Chatterbox on XMPP

The custom Chatterbox v1 protocol and XMPP cover the same features, so the XMPP variant of Chatterbox is mostly a mapping exercise:

Chatterbox v1 XMPP equivalent Specification

hello (token, resume)

SASL (token or SCRAM), Bind 2, Stream Management resume

RFC 6120, XEP-0388, XEP-0386, XEP-0198

join (room, since)

Directed presence to the room with a history request

XEP-0045

leave

presence type="unavailable" to the room

XEP-0045

message (id, text, replyTo)

message type="groupchat" with id, body and a reply reference

RFC 6121, XEP-0461

edit

A new message with a replace element naming the original id

XEP-0308

react

A reactions element naming the target id

XEP-0444

typing

Chat state composing and paused

XEP-0085

receipt (delivered, read)

received and displayed elements

XEP-0184, XEP-0333

presence and the heartbeat

presence plus XMPP Ping and Stream Management acks

XEP-0199, XEP-0198

history (before, limit)

An archive iq with a result-set page

XEP-0313

ack and error

Message reflection, Stream Management acks and iq results, and stanza errors

RFC 6120

attachments

HTTP upload slot and an out-of-band URL

XEP-0363

The remaining pages of the group build this mapping: the servers, the browser libraries, the full feature set with stanza examples, an embeddable client and the optional encryption and calls.

XMPP or a custom protocol

XMPP is a good fit when the product wants what XMPP already is:

  • Standard features that already exist. Rooms with roles and affiliations, history with paging, receipts, corrections, file upload, push notifications for mobile and end-to-end encryption are specified, implemented in several servers and in off-the-shelf clients such as Converse.js. A custom protocol re-invents each of them.

  • Federation or many clients. If users must reach other domains, or if third-party and native clients should connect, a standard protocol is a requirement rather than an option.

  • Ready servers and clients. ejabberd, Prosody, Openfire and MongooseIM scale, cluster and monitor out of the box.

A custom WebSocket protocol is the better fit when:

  • the messages are not chat-shaped (a game’s input and snapshots, or a binary media stream);

  • the product needs a data model XMPP does not provide (threads with per-conversation sequence numbers, rich cards, or a proprietary permission model), and mapping it onto stanza extensions costs more than writing the protocol;

  • the team does not want to operate an XMPP server, or wants one codebase and one language for server and client; or

  • the payload is dominated by non-text data: XML is verbose and a binary or JSON protocol is smaller.

A hosted chat service is a third option and takes the operations away at the cost of lock-in; the architecture page Chat Architecture compares the three for Chatterbox. A one-line note on a related project: Matrix is a separate federated protocol with a different model (a replicated event graph per room rather than stanzas routed between servers); this section does not cover it.

Security

  • TLS is mandatory. RFC 6120 requires servers and clients to support TLS, and RFC 7590 describes how to use it. In a browser that means wss:// for the stream and https:// for the host-meta file. The plain ws:// capture above is for a loopback development server only.

  • Allow-list Origin in the reverse proxy. A browser attaches the page’s Origin to the WebSocket upgrade, but the ejabberd 26.09 used on these pages accepted the upgrade from any origin. Reject unexpected origins at the proxy in front of the server (see Proxies, Load Balancers and Deployment). The XMPP login still has to succeed afterwards, but an open origin lets any web page drive a stream that holds a user’s session token.

  • Choose the authentication mechanism deliberately. Offer SCRAM-SHA-256 or SCRAM-SHA-512, keep PLAIN for TLS-protected legacy backends, and prefer tokens (OAUTHBEARER, X-OAUTH2 or FAST) so the page never stores a password. SASL2 lets the server limit what an unauthenticated client may do before the exchange finishes.

  • Registration and account enumeration. In-band registration lets anyone create accounts; close it or put it behind a CAPTCHA, an invitation or an e-mail check. Error replies and presence subscriptions can reveal whether a JID exists, so rate-limit them.

  • The roster and presence leak information. A subscribed presence shows a contact when you are online, your status text and your resources. Do not auto-accept subscriptions, and remember that a full JID reveals the resource name.

  • Federation. Server-to-server streams bring spam and abuse from other domains. Disable them for a private deployment, or restrict them to an allow-list of domains, and require TLS and certificate validation between servers.

  • Addresses. Normalise JIDs as RFC 7622 describes before comparing them or using them for access control, because look-alike Unicode characters make two different strings look like one name.

  • End-to-end encryption. TLS protects the hops, but the server sees message bodies. See OMEMO Encryption for the end-to-end option.

Version notes

  • Core RFCs. RFC 6120 and RFC 6121 (2011) obsolete RFC 3920 and RFC 3921, and RFC 7622 (2015) obsoletes RFC 6122 for addresses. Older tutorials that cite the 2004 documents describe the same protocol with an older address format.

  • WebSocket. RFC 7395 (2014) is the framing used here: subprotocol xmpp, one element per message and the urn:ietf:params:xml:ns:xmpp-framing namespace. A client finds the endpoint through /.well-known/host-meta (XEP-0156). BOSH (XEP-0124 and XEP-0206) is the older fallback and is still supported by most servers.

  • Authentication. Classic SASL with a stream restart and a separate bind is what every server supports. SASL2 (XEP-0388), Bind 2 (XEP-0386) and FAST (XEP-0484) are newer and not universal; check the stream features. Non-SASL authentication (XEP-0078) is obsolete.

  • Compliance Suites. XEP-0479 (2023 edition) is the newest published suite. Its status is Experimental, so read it as a guide to what current clients implement and not as a certification.

  • Servers and libraries. The examples use ejabberd 26.09, Strophe.js 5.0.0 and @xmpp/client 0.14.0. A server that advertises other namespace versions than the ones shown is the authority; read disco#info before relying on one.

Common errors and pitfalls

  • No messages after login. The client never sent initial presence. Send an empty presence after the roster fetch.

  • Using the full JID as the user identifier. The resource changes on every connection. Store the bare JID.

  • Comparing JIDs as raw strings. Case folding and PRECIS rules mean Alice@Example.test equals alice@example.test. Parse and normalise first (Strophe.js has Strophe.getBareJidFromJid, @xmpp/jid has a jid() function).

  • Ignoring type="error". A failed iq or message comes back as an error stanza and a client that drops it never shows the user that, say, a room is members-only.

  • Treating a message as acknowledged. The core protocol gives no delivery guarantee. Use Stream Management acks for "the server got it" and receipts (XEP-0184) for "the other client got it".

  • Putting the password in the page. Prefer a short-lived token (OAUTHBEARER, X-OAUTH2 or FAST) over a stored password.

  • Leaving federation open on a private server. Disable server-to-server connections or restrict them to an allow-list if Chatterbox does not need federation.