A few properties set Matrix apart from the typical client-server chat you know:
m.room.message, m.room.member, m.reaction…) cover the everyday case; custom types let you build polls, maps, CRDTs, collaborative editors, game state, IoT telemetry — anything that wants a decentralized replicated log with auth.curl.| Layer | Left side | Middle | Right side |
|---|---|---|---|
| Clients | Element (web) | Your homeserver: your.hagency.cloud | FluffyChat (mobile) |
| Federation | Your homeserver uses the Server-Server API | your.hagency.cloud | Remote homeserver: matrix.org |
| Extensions | Bridges, appservices | Attach to your.hagency.cloud | Appservices, bots |
Flow summary:
Element -> your.hagency.cloud over the Client-Server API.
FluffyChat -> its selected homeserver over the Client-Server API.
your.hagency.cloud <-> matrix.org over the Server-Server API (federation).
Bridges, appservices, and bots extend a homeserver locally rather than speaking federation directly.
Clients talk the Client-Server API: HTTPS + JSON, defined in the spec. Element, FluffyChat, Nheko, Cinny, your own bot — all of them hit the same endpoints on your homeserver.
Homeservers talk the Server-Server API (federation) to each other over HTTPS + signed JSON. This is how a user on your-name.hagency.cloud can chat with a user on matrix.org without a middleman.
Bridges and appservices attach to a homeserver and extend it with virtual users and rooms (see Bridges and Appservices).
You pick the client, Hagency runs the homeserver, the protocol does the rest.
When Element or any client talks to your homeserver, it does one of four things:
/sync — a long-poll that returns everything new since the last request: new events in rooms, new invites, presence changes, to-device messages. Clients keep one /sync open at all times; the server holds it open for up to 30 seconds and then returns whatever arrived.PUT /rooms/{id}/send/{event_type}/{txn_id} — sends a new event (a message, a reaction, a redaction). The txn_id makes sends idempotent so retries don't duplicate.mxc:// URI.Everything is authenticated with a bearer access token obtained at login. See the Matrix client-server spec for the full API.
When your homeserver needs to send a message into a room that has members on another server, it:
The remote server doesn't trust the sender blindly — it verifies every event's signature and checks that the event's "auth chain" (the sequence of auth events that justify why this event is allowed at all) is valid according to the room's power-level rules.
If two servers add events concurrently, the state resolution algorithm picks a canonical ordering based on the events' auth_events and a deterministic tie-breaker. Both servers end up with the same final state.
Every event in a room contains a prev_events field listing the hashes of events it was sent in response to. With many participants across many servers, prev_events can point at multiple predecessors, forming a directed acyclic graph. Clients walk the DAG backwards to show you history; servers use it to detect and heal forks when federation catches up after a partition.
A room is a shared, replicated event log with auth rules. Every room has:
!abc123:server#general:serverRooms can be public (listed in the homeserver's directory) or private (invite-only). They're not scoped to a homeserver — a room that originated on matrix.org can have members on your-name.hagency.cloud, on kde.org, and on element.io all simultaneously, and every homeserver replicates its own copy of the events.
A space is a special kind of room whose "members" are other rooms and other spaces. Spaces are Matrix's answer to "I want a Slack workspace" or "I want a Discord server" — they group related rooms into a browseable hierarchy. You can nest spaces, mark some rooms as "suggested", and selectively share parts of a space with outsiders.
Under the hood a space is just a room with a specific room type (m.space) and m.space.child state events pointing at its children.