| body | string | A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'. |
| info | {VideoInfo} | Metadata about the video clip referred to in url. |
| msgtype | string | Must be 'm.video'. |
| url | string | The URL to the video clip. |
VideoInfo
| VideoInfo Key | Type | Description |
|---|---|---|
| duration | integer | The duration of the video in milliseconds. |
| h | integer | The height of the video in pixels. |
| mimetype | string | The mimetype of the video e.g. video/mp4. |
| size | integer | The size of the video in bytes. |
| thumbnail_info | {ImageInfo} | |
| thumbnail_url | string | The URL to a thumbnail of the video clip. |
| w | integer | The width of the video in pixels. |
Example:
{ "age": 146, "content": { "body": "Gangnam Style", "info": { "duration": 2140786, "h": 320, "mimetype": "video/mp4", "size": 1563685, "thumbnail_info": { "h": 300, "mimetype": "image/jpeg", "size": 46144, "w": 300 }, "thumbnail_url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe", "w": 480 }, "msgtype": "m.video", "url": "mxc://localhost/a526eYUSFFxlgbQYZmo442" }, "event_id": "$143273582443PhrSn:localhost", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", "user_id": "@example:localhost" }
This message represents a single audio clip.
| Content Key | Type | Description |
|---|---|---|
| body | string | A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'. |
| info | {AudioInfo} | Metadata for the audio clip referred to in url. |
| msgtype | string | Must be 'm.audio'. |
| url | string | The URL to the audio clip. |
AudioInfo
| AudioInfo Key | Type | Description |
|---|---|---|
| duration | integer | The duration of the audio in milliseconds. |
| mimetype | string | The mimetype of the audio e.g. audio/aac. |
| size | integer | The size of the audio clip in bytes. |
Example:
{ "age": 146, "content": { "body": "Bee Gees - Stayin' Alive", "info": { "duration": 2140786, "mimetype": "audio/mpeg", "size": 1563685 }, "msgtype": "m.audio", "url": "mxc://localhost/ffed755USFFxlgbQYZGtryd" }, "event_id": "$143273582443PhrSn:localhost", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", "user_id": "@example:localhost" }
Clients SHOULD verify the structure of incoming events to ensure that the expected keys exist and that they are of the right type. Clients can discard malformed events or display a placeholder message to the user. Redacted m.room.message events MUST be removed from the client. This can either be replaced with placeholder text (e.g. "[REDACTED]") or the redacted message can be removed entirely from the messages view.
Events which have attachments (e.g. m.image, m.file) SHOULD be uploaded using the content repository module where available. The resulting mxc:// URI can then be used in the url key.
Clients can send messages using POST or PUT requests. Clients SHOULD use PUT requests with transaction IDs to make requests idempotent. This ensures that messages are sent exactly once even under poor network conditions. Clients SHOULD retry requests using an exponential-backoff algorithm for a certain amount of time T. It is recommended that T is no longer than 5 minutes. After this time, the client should stop retrying and mark the message as "unsent". Users should be able to manually resend unsent messages.
Users may type several messages at once and send them all in quick succession. Clients SHOULD preserve the order in which they were sent by the user. This means that clients should wait for the response to the previous request before sending the next request. This can lead to head-of-line blocking. In order to reduce the impact of head-of-line blocking, clients should use a queue per room rather than a global queue, as ordering is only relevant within a single room rather than between rooms.
Messages SHOULD appear immediately in the message view when a user presses the "send" button. This should occur even if the message is still sending. This is referred to as "local echo". Clients SHOULD implement "local echo" of messages. Clients MAY display messages in a different format to indicate that the server has not processed the message. This format should be removed when the server responds.
Clients need to be able to match the message they are sending with the same message which they receive from the event stream. The echo of the same message from the event stream is referred to as "remote echo". Both echoes need to be identified as the same message in order to prevent duplicate messages being displayed. Ideally this pairing would occur transparently to the user: the UI would not flicker as it transitions from local to remote. Flickering cannot be fully avoided in the current client-server API. Two scenarios need to be considered:
In the first scenario, the client will receive an event ID when the request to send the message completes. This ID can be used to identify the duplicate event when it arrives on the event stream. However, in the second scenario, the event arrives before the client has obtained an event ID. This makes it impossible to identify it as a duplicate event. This results in the client displaying the message twice for a fraction of a second before the the original request to send the message completes. Once it completes, the client can take remedial actions to remove the duplicate event by looking for duplicate event IDs. A future version of the client-server API will resolve this by attaching the transaction ID of the sending request to the event itself.
Clients may wish to show the human-readable display name of a room member as part of a membership list, or when they send a message. However, different members may have conflicting display names. Display names MUST be disambiguated before showing them to the user, in order to prevent spoofing of other users.
To ensure this is done consistently across clients, clients SHOULD use the following algorithm to calculate a disambiguated display name for a given user:
Developers should take note of the following when implementing the above algorithm:
Clients may wish to show the display name and avatar URL of the room member who sent a message. This can be achieved by inspecting the m.room.member state event for that user ID (see Calculating the display name for a user).
When a user paginates the message history, clients may wish to show the historical display name and avatar URL for a room member. This is possible because older m.room.member events are returned when paginating. This can be implemented efficiently by keeping two sets of room state: old and current. As new events arrive and/or the user paginates back in time, these two sets of state diverge from each other. New events update the current state and paginated events update the old state. When paginated events are processed sequentially, the old state represents the state of the room at the time the event was sent. This can then be used to set the historical display name and avatar URL.
Clients may wish to show a human-readable name for a room. There are a number of possibilities for choosing a useful name. To ensure that rooms are named consistently across clients, clients SHOULD use the following algorithm to choose a name:
If the room has an m.room.name state event, use the name given by that event.
If the room has an m.room.canonical_alias state event, use the alias given by that event.
If neither of the above events are present, a name should be composed based on the members of the room. Clients should consider m.room.member events for users other than the logged-in user, with membership: join or membership: invite.
If there is only one such event, the display name for the room should be the disambiguated display name of the corresponding user.
If there are two such events, they should be lexicographically sorted by their state_key (i.e. the corresponding user IDs), and the display name for the room should be the disambiguated display name of both users: "<user1> and <user2>", or a localised variant thereof.
If there are three or more such events, the display name for the room should be based on the disambiguated display name of the user corresponding to the first such event, under a lexicographical sorting according to their state_key. The display name should be in the format "<user1> and <N> others" (or a localised variant thereof), where N is the number of m.room.member events with membership: join or membership: invite, excluding the logged-in user and "user1".
For example, if Alice joins a room, where Bob (whose user id is @superuser:example.com), Carol (user id @carol:example.com) and Dan (user id @dan:matrix.org) are in conversation, Alice's client should show the room name as "Carol and 2 others".
If the room has no m.room.name or m.room.canonical_alias events, and no active members other than the current user, clients should consider m.room.member events with membership: leave. If such events exist, a display name such as "Empty room (was <user1> and <N> others)" (or a localised variant thereof) should be used, following similar rules as for active members (see above).
A complete absence of m.room.name, m.room.canonical_alias, and m.room.member events is likely to indicate a problem with creating the room or synchronising the state table; however clients should still handle this situation. A display name such as "Empty room" (or a localised variant thereof) should be used in this situation.
Clients SHOULD NOT use m.room.aliases events as a source for room names, as it is difficult for clients to agree on the best alias to use, and aliases can change unexpectedly.
Homeservers SHOULD reject m.room.message events which don't have a msgtype key, or which don't have a textual body key, with an HTTP status code of 400.
Messages sent using this module are not encrypted. Messages can be encrypted using the E2E module.
Clients should sanitise all displayed keys for unsafe HTML to prevent Cross-Site Scripting (XSS) attacks. This includes room names and topics.
This module outlines how two users in a room can set up a Voice over IP (VoIP) call to each other. Voice and video calls are built upon the WebRTC 1.0 standard. Call signalling is achieved by sending message events to the room. As a result, this means that clients MUST only send call events to rooms with exactly two participants as currently the WebRTC standard is based around two-party communication.
Message Event
This event is sent by the caller when they wish to establish a call.
| Content Key | Type | Description |
|---|---|---|
| call_id | string | A unique identifer for the call. |
| lifetime | integer | The time in milliseconds that the invite is valid for. Once the invite age exceeds this value, clients should discard it. They should also no longer show the call as awaiting an answer in the UI. |
| offer | {Offer} | The session description object |
| version | integer | The version of the VoIP specification this message adheres to. This specification is version 0. |
Offer
| Offer Key | Type | Description |
|---|---|---|
| sdp | string | The SDP text of the session description. |
| type | string | The type of session description. Must be 'offer'. |
Example:
{ "age": 242352, "content": { "call_id": "12345", "lifetime": 60000, "offer": { "sdp": "v=0\r\no=-<PHONE_NUMBER>695956864 2 IN IP4 <IP_ADDRESS>[...]", "type": "offer" }, "version": 0 }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "type": "m.call.invite", "user_id": "@example:localhost" }
Message Event
This event is sent by callers after sending an invite and by the callee after answering. Its purpose is to give the other party additional ICE candidates to try using to communicate.
| Content Key | Type | Description |
|---|---|---|
| call_id | string | The ID of the call this event relates to. |
| candidates | [Candidate] | Array of objects describing the candidates. |
| version | integer | The version of the VoIP specification this messages adheres to. This specification is version 0. |
Candidate
| Candidate Key | Type | Description |
|---|---|---|
| candidate | string | The SDP 'a' line of the candidate. |
| sdpMLineIndex | number | The index of the SDP 'm' line this candidate is intended for. |
| sdpMid | string | The SDP media type this candidate is intended for. |
Example:
{ "age": 242352, "content": { "call_id": "12345", "candidates": [ { "candidate": "candidate:863018703 1 udp<PHONE_NUMBER> <IP_ADDRESS> 43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" } ], "version": 0 }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "type": "m.call.candidates", "user_id": "@example:localhost" }
Message Event
This event is sent by the callee when they wish to answer the call.
| Content Key | Type | Description |
|---|---|---|
| answer | {Answer} | The session description object |
| call_id | string | The ID of the call this event relates to. |
| version | number |
Answer
| Answer Key | Type | Description |
|---|---|---|
| sdp | string | The SDP text of the session description. |
| type | string | The type of session description. Must be 'answer'. |
Example:
{ "age": 242352, "content": { "answer": { "sdp": "v=0\r\no=-<PHONE_NUMBER>695956864 2 IN IP4 <IP_ADDRESS>[...]", "type": "answer" }, "call_id": "12345", "lifetime": 60000, "version": 0 }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "type": "m.call.answer", "user_id": "@example:localhost" }
Message Event
Sent by either party to signal their termination of the call. This can be sent either once the call has has been established or before to abort the call.
| Content Key | Type | Description |
|---|---|---|
| call_id | string | The ID of the call this event relates to. |
| version | integer | The version of the VoIP specification this message adheres to. This specification is version 0. |
Example:
{ "age": 242352, "content": { "call_id": "12345", "version": 0 }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "type": "m.call.hangup", "user_id": "@example:localhost" }
A call is set up with message events exchanged as follows:
Caller Callee [Place Call] m.call.invite -----------> m.call.candidate --------> [..candidates..] --------> [Answers call] <--------------- m.call.answer [Call is active and ongoing] <--------------- m.call.hangup
Or a rejected call:
Caller Callee m.call.invite ------------> m.call.candidate ---------> [..candidates..] ---------> [Rejects call] <-------------- m.call.hangup
Calls are negotiated according to the WebRTC specification.
"Glare" is a problem which occurs when two users call each other at roughly the same time. This results in the call failing to set up as there already is an incoming/outgoing call. A glare resolution algorithm can be used to determine which call to hangup and which call to answer. If both clients implement the same algorithm then they will both select the same call and the call will be successfully connected.
As calls are "placed" to rooms rather than users, the glare resolution algorithm outlined below is only considered for calls which are to the same room. The algorithm is as follows:
The call setup should appear seamless to the user as if they had simply placed a call and the other party had accepted. This means any media stream that had been setup for use on a call should be transferred and used for the call that replaces it.
The homeserver MAY provide a TURN server which clients can use to contact the remote party. The following HTTP API endpoints will be used by clients in order to get information about the TURN server.
This API provides credentials for the client to use when initiating calls.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
No parameters
Response format:
| Parameter | Type | Description |
|---|---|---|
| password | string | The password to use. |
| ttl | integer | The time-to-live in seconds |
| uris | [string] | A list of TURN URIs |
| username | string | The username to use. |
Example request:
GET /_matrix/client/api/v1/turnServer HTTP/1.1
Response:
Status code 200:
The TURN server credentials.
Example
{ "username":"1443779631:@user:example.com", "password":"JlKfBy1QwLrO20385QyAtEyIv0=", "uris":[ "turn:turn.example.com:3478?transport=udp", "turn:<IP_ADDRESS>:3478?transport=tcp", "turns:<IP_ADDRESS>:443?transport=tcp" ], "ttl":86400 }
Calls should only be placed to rooms with one other user in them. If they are placed to group chat rooms it is possible that another user will intercept and answer the call.
Users may wish to be informed when another user is typing in a room. This can be achieved using typing notifications. These are ephemeral events scoped to a room_id. This means they do not form part of the Event Graph but still have a room_id key.
Typing Event
Informs the client of the list of users currently typing.
| Content Key | Type | Description |
|---|---|---|
| user_ids | [string] | The list of user IDs typing in this room, if any. |
Example:
{ "content": { "user_ids": [ "@alice:matrix.org", "@bob:example.com" ] }, "room_id": "!z0mnsuiwhifuhwwfw:matrix.org", "type": "m.typing" }
When a client receives an m.typing event, it MUST use the user ID list to REPLACE its knowledge of every user who is currently typing. The reason for this is that the server does not remember users who are not currently typing as that list gets big quickly. The client should mark as not typing any user ID who is not in that list.
It is recommended that clients store a boolean indicating whether the user is typing or not.  The value of N is recommended to be no more than 20-30 seconds. This request should be re-sent by the client to continue informing the server the user is still typing. As subsequent requests will replace older requests, a safety margin of 5 seconds before the expected timeout runs out is recommended. When the user stops typing, the state change of the boolean to false should trigger another HTTP request to inform the server that the user has stopped typing.
This tells the server that the user is typing for the next N milliseconds where N is the value specified in the timeout key. Alternatively, if typing is false, it tells the server that the user has stopped typing.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user who has started to type. |
| roomId | string | Required. The room in which the user is typing. |
| JSON body parameters | ||
| timeout | integer | The length of time in milliseconds to mark this user as typing. |
| typing | boolean | Required. Whether the user is typing or not. If false, the timeout key can be omitted. |
Example request:
PUT /_matrix/client/api/v1/rooms/%21wefh3sfukhs%3Aexample.com/typing/%40alice%3Aexample.com HTTP/1.1 Content-Type: application/json { "typing": true, "timeout": 30000 }
Response:
Status code 200:
The new typing state was set.
Example
{}
Servers MUST emit typing EDUs in a different form to m.typing events which are shown to clients. This form looks like:
{ "type": "m.typing", "content": { "room_id": "!room-id-here:matrix.org", "user_id": "@user-id-here:matrix.org", "typing": true/false } }
This does not contain timing information so it is up to originating homeservers to ensure they eventually send "stop" notifications.
Clients may not wish to inform everyone in a room that they are typing and instead only specific users in the room.
This module adds in support for receipts. These receipts are a form of acknowledgement of an event. This module defines a single acknowledgement: m.read which indicates that the user has read up to a given event.
Sending a receipt for each event can result in sending large amounts of traffic to a homeserver. To prevent this from becoming a problem, receipts are implemented using "up to" markers. This marker indicates that the acknowledgement applies to all events "up to and including" the event specified. For example, marking an event as "read" would indicate that the user had read all events up to the referenced event.
Each user_id, receipt_type pair must be associated with only a single event_id.
Receipt Event
Informs the client of new receipts.
| Content Key | Type | Description |
|---|---|---|
| $EVENT_ID | {Receipts} | The mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of the event being acknowledged and not an ID for the receipt itself. |
Receipts
| Receipts Key | Type | Description |
|---|---|---|
| m.read | {Users} | A collection of users who have sent m.read receipts for this event. |
Users
| Users Key | Type | Description |
|---|---|---|
| $USER_ID | {Receipt} | The mapping of user ID to receipt. The user ID is the entity who sent this receipt. |
Receipt
| Receipt Key | Type | Description |
|---|---|---|
| ts | number | The timestamp the receipt was sent at. |
Example:
{ "content": { "$1435641916114394fHBLK:matrix.org": { "m.read": { "@rikj:jki.re": { "ts":<PHONE_NUMBER>453 } } } }, "room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org", "type": "m.receipt" }
In v1 /initialSync, receipts are listed in a separate top level receipts key. In v2 /sync, receipts are contained in the ephemeral block for a room. New receipts that come down the event streams are deltas which update existing mappings. Clients should replace older receipt acknowledgements based on user_id and receipt_type pairs. For example:
Client receives m.receipt: user = @alice:example.com receipt_type = m.read event_id = $aaa:example.com Client receives another m.receipt: user = @alice:example.com receipt_type = m.read event_id = $bbb:example.com The client should replace the older acknowledgement for $aaa:example.com with this one for $bbb:example.com
Clients should send read receipts when there is some certainty that the event in question has been displayed to the user. Simply receiving an event does not provide enough certainty that the user has seen the event. The user SHOULD need to take some action such as viewing the room that the event was sent to or dismissing a notification in order for the event to count as "read".
A client can update the markers for its user by interacting with the following HTTP APIs.
This API updates the marker for the given receipt type to the event ID specified.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| roomId | string | Required. The room in which to send the event. |
| receiptType | enum | Required. The type of receipt to send. One of: ["m.read"] |
| eventId | string | Required. The event ID to acknowledge up to. |
Example request:
POST /_matrix/client/v2_alpha/rooms/%21wefuh21ffskfuh345%3Aexample.com/receipt/m.read/%241924376522eioj%3Aexample.com HTTP/1.1 Content-Type: application/json {}
Response:
Status code 200:
The receipt was sent.
Example
{}
For efficiency, receipts SHOULD be batched into one event per room before delivering them to clients.
Receipts are sent across federation as EDUs with type m.receipt. The format of the EDUs are:
{ <room_id>: { <receipt_type>: { <user_id>: { <content> } }, ... }, ... }
These are always sent as deltas to previously sent receipts. Currently only a single <receipt_type> should be used: m.read.
As receipts are sent outside the context of the event graph, there are no integrity checks performed on the contents of m.receipt events.
Each user has the concept of presence information. This encodes:
This information is collated from both per-device (online, idle, last_active) and per-user (status) data, aggregated by the user's homeserver and transmitted as an m.presence event. This is one of the few events which are sent outside the context of a room. Presence events are sent to all users who subscribe to this user's presence through a presence list or by sharing membership of a room.
A presence list is a list of user IDs whose presence the user wants to follow. To be added to this list, the user being added must be invited by the list owner who must accept the invitation.
User's presence state is represented by the presence key, which is an enum of one of the following:
Presence Event
Informs the client of a user's presence state change.
| Content Key | Type | Description |
|---|---|---|
| avatar_url | string | The current avatar URL for this user, if any. |
| displayname | string | The current display name for this user, if any. |
| last_active_ago | number | The last time since this used performed some action, in milliseconds. |
| presence | enum | The presence state for this user. One of: ["online", "offline", "unavailable", "free_for_chat", "hidden"] |
| user_id | string | The user's ID. |
Example:
{ "content": { "avatar_url": "mxc://localhost:wefuiwegh8742w", "last_active_ago": 2478593, "presence": "online", "user_id": "@example:localhost" }, "event_id": "$WLGTSEFSEF:localhost", "type": "m.presence" }
Clients can manually set/get their presence/presence list using the HTTP APIs listed below.
This API sets the given user's presence state. When setting the status, the activity time is updated to reflect that activity; the client does not need to specify the last_active_ago field. You cannot set the presence state of another user.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user whose presence state to update. |
| JSON body parameters | ||
| status_msg | string | The status message to attach to this state. |
| presence | string | Required. The new presence state. |
Example request:
PUT /_matrix/client/api/v1/presence/%40alice%3Aexample.com/status HTTP/1.1 Content-Type: application/json { "presence": "online", "status_msg": "I am here." }
Response:
Status code 200:
The new presence state was set.
Example
{}
Get the given user's presence state.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user whose presence state to get. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| last_active_ago | integer | The length of time in milliseconds since an action was performed by this user. |
| presence | enum | This user's presence. One of: ["online", "offline", "unavailable", "free_for_chat"] |
| status_msg | string or null | The state message for this user if one was set. |
Example request:
GET /_matrix/client/api/v1/presence/%40alice%3Aexample.com/status HTTP/1.1
Response:
Status code 200:
The presence state for this user.
Example
{ "presence": "unavailable", "last_active_ago": 420845, "status_msg": null }
Adds or removes users from this presence list.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user whose presence list is being modified. |
| JSON body parameters | ||
| drop | array[string] | A list of user IDs to remove from the list. |
| invite | array[string] | A list of user IDs to add to the list. |
Example request:
POST /_matrix/client/api/v1/presence/list/%40alice%3Aexample.com HTTP/1.1 Content-Type: application/json { "invite": [ "@bob:matrix.org" ], "drop": [ "@alice:matrix.org" ] }
Response:
Status code 200:
The list was updated.
Example
{}
Retrieve a list of presence events for every user on this list.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user whose presence list should be retrieved. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| N/A | [PresenceEvent] |
Example request:
GET /_matrix/client/api/v1/presence/list/%40alice%3Aexample.com HTTP/1.1
Response:
Status code 200:
A list of presence events for this list.
Example
[ { "content": { "avatar_url": "mxc://matrix.org/AfwefuigfWEfhuiPP", "displayname": "Alice Margatroid", "last_active_ago": 395, "presence": "offline", "user_id": "@alice:matrix.org" }, "type": "m.presence" }, { "content": { "avatar_url": "mxc://matrix.org/FWEhuiwegfWEfhuiwf", "displayname": "Marisa Kirisame", "last_active_ago": 16874, "presence": "online", "user_id": "@marisa:matrix.org" }, "type": "m.presence" } ]
Clients SHOULD implement an "idle timeout". This is a timer which fires after a period of inactivity on the client. The definition of inactivity varies depending on the client. For example, web implementations may determine inactivity to be not moving the mouse for a certain period of time. When this timer fires it should set the presence state to unavailable. When the user becomes active again (e.g. by moving the mouse) the client should set the presence state to online. A timeout value between 1 and 5 minutes is recommended.
Each user's home server stores a "presence list" per user. Once a user accepts a presence list, both user's HSes must track the subscription.
Because the profile display name and avatar information are likely to be used in many places of a client's display, changes to these fields SHOULD cause an automatic propagation event to occur, informing likely-interested parties of the new values. One of these change mechanisms SHOULD be via m.presence events. These events should set displayname and avatar_url to the new values along with the presence-specific keys. This SHOULD be done automatically by the home server when a user successfully changes their display name or avatar URL.
Rationale
The intention for sending this information in m.presence is so that any "user list" can display the current name/presence for a user ID outside the scope of a room e.g. for a user page. This is bundled into a single event for several reasons. The user's display name can change per room. This event provides the "canonical" name for the user. In addition, the name is bundled into a single event for the ease of client implementations. If this was not done, the client would need to search all rooms for their own membership event to pull out the display name.
The server maintains a timestamp of the last time it saw a pro-active event from the user. A pro-active event may be sending a message to a room or changing presence state to a higher level of availability. Levels of availability are defined from low to high as follows:
Based on this list, changing state from unavailable to online counts as a pro-active event, whereas online to unavailable does not. This timestamp is presented via a key called last_active_ago which gives the relative number of milliseconds since the pro-active event.
Presence information is shared with all users who share a room with the target user. In large public rooms this could be undesirable.
This module allows users to upload content to their homeserver which is retrievable from other homeservers. Its' purpose is to allow users to share attachments in a room. Content locations are represented as Matrix Content (MXC) URIs. They look like:
mxc://<server-name>/<media-id> <server-name> : The name of the homeserver where this content originated, e.g. matrix.org <media-id> : An opaque ID which identifies the content.
Uploads are POSTed to a resource on the user's local homeserver which returns a token which is used to GET the download. Content is downloaded from the recipient's local homeserver, which must first transfer the content from the origin homeserver using the same API (unless the origin and destination homeservers are the same).
Clients can upload and download content using the following HTTP APIs.
Download content from the content repository.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| serverName | string | Required. The server name from the mxc:// URI (the authoritory component) |
| mediaId | string | Required. The media ID from the mxc:// URI (the path component) |
Response format:
| Parameter | Type | Description |
|---|---|---|
| <file> | file | The content that was previously uploaded. |
| Content-Type | Header<string> | The content type of the file that was previously uploaded. |
| Content-Disposition | Header<string> | The name of the file that was previously uploaded, if set. |
Example request:
GET /_matrix/media/v1/download/matrix.org/ascERGshawAWawugaAcauga HTTP/1.1
Download a thumbnail of the content from the content repository.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| serverName | string | Required. The server name from the mxc:// URI (the authoritory component) |
| mediaId | string | Required. The media ID from the mxc:// URI (the path component) |
| query parameters | ||
| width | integer | The desired width of the thumbnail. The actual thumbnail may not match the size specified. |
| height | integer | The desired height of the thumbnail. The actual thumbnail may not match the size specified. |
| method | enum | The desired resizing method. One of: ["crop", "scale"] |
Response format:
| Parameter | Type | Description |
|---|---|---|
| <file> | file | A thumbnail of the requested content. |
| Content-Type | Header<string> | The content type of the thumbnail. |
Example request:
GET /_matrix/media/v1/thumbnail/matrix.org/ascERGshawAWawugaAcauga?width=64&height=64&method=scale HTTP/1.1
Upload some content to the content repository.
Request format:
| Parameter | Type | Description |
|---|---|---|
| body parameters | ||
| <content> | byte | Required. The content to be uploaded. |
| header parameters | ||
| Content-Type | string | The content type of the file being uploaded |
Response format:
| Parameter | Type | Description |
|---|---|---|
| content_uri | string | The MXC URI to the uploaded content. |
Example request:
POST /_matrix/media/v1/upload HTTP/1.1 Content-Type: application/json <bytes>
Response:
Status code 200:
The MXC URI for the uploaded content.
Example
{ "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" }
The thumbnail methods are "crop" and "scale". "scale" tries to return an image where either the width or the height is smaller than the requested size. The client should then scale and letterbox the image if it needs to fit within a given rectangle. "crop" tries to return an image where the width and height are close to the requested size and the aspect matches the requested size. The client should scale the image if it needs to fit within a given rectangle.
Homeservers may generate thumbnails for content uploaded to remote homeservers themselves or may rely on the remote homeserver to thumbnail the content. Homeservers may return thumbnails of a different size to that requested. However homeservers should provide exact matches where reasonable. Homeservers must never upscale images.
The HTTP GET endpoint does not require any authentication. Knowing the URL of the content is sufficient to retrieve the content, even if the entity isn't in the room.
MXC URIs are vulnerable to directory traversal attacks such as mxc://<IP_ADDRESS>/../../../some_service/etc/passwd. This would cause the target homeserver to try to access and return this file. As such, homeservers MUST sanitise MXC URIs by allowing only alphanumeric (A-Za-z0-9), _ and - characters in the server-name and media-id values. This set of whitelisted characters allows URL-safe base64 encodings specified in RFC 4648. Applying this character whitelist is preferable to blacklisting . and / as there are techniques around blacklisted characters (percent-encoded characters, UTF-8 encoded traversals, etc).
Homeservers have additional content-specific concerns:
Matrix optionally supports end-to-end encryption, allowing rooms to be created whose conversation contents is not decryptable or interceptable on any of the participating homeservers.
End-to-end crypto is still being designed and prototyped - notes on the design may be found at https://lwn.net/Articles/634144/
This module adds support for controlling the visibility of previous events in a room.
In all cases except world_readable, a user needs to join a room to view events in that room. Once they have joined a room, they will gain access to a subset of events in the room. How this subset is chosen is controlled by the m.room.history_visibility event outlined below. After a user has left a room, they may seen any events which they were allowed to see before they left the room, but no events received after they left.
The four options for the m.room.history_visibility event are:
Warning
These options are applied at the point an event is sent. Checks are performed with the state of the m.room.history_visibility event when the event in question is added to the DAG. This means clients cannot retrospectively choose to show or hide history to new users if the setting at that time was more restrictive.
This event controls whether a user can see the events that happened in a room from before they joined.
| Content Key | Type | Description |
|---|---|---|
| history_visibility | enum | Who can see the room history. One of: ["invited", "joined", "shared", "world_readable"] |
Example:
{ "age": 242352, "content": { "history_visibility": "shared" }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "state_key": "", "type": "m.room.history_visibility", "user_id": "@example:localhost" }
Clients that implement this module MUST present to the user the possible options for setting history visibility when creating a room.
Clients may want to display a notice that their events may be read by non-joined people if the value is set to world_readable.
By default if no history_visibility is set, or if the value is not understood, the visibility is assumed to be shared. The rules governing whether a user is allowed to see an event depend solely on the state of the room at that event:
The default value for history_visibility is shared for backwards-compatibility reasons.  Homeservers send notifications of events to user-configured HTTP endpoints. Users may also configure a number of rules that determine which events generate notifications. These are all stored and managed by the user's homeserver. This allows user-specific push settings to be reused between client applications.
The above diagram shows the flow of push notifications being sent to a handset where push notifications are submitted via the handset vendor, such as Apple's APNS or Google's GCM. This happens as follows:
Definitions for terms used in this section are below:
Clients MUST configure a Pusher before they will receive push notifications. There is a single API endpoint for this, as described below.
This endpoint allows the creation, modification and deletion of pushers for this user ID. The behaviour of this endpoint varies depending on the values in the JSON body.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| JSON body parameters | ||
| lang | string | Required. The preferred language for receiving notifications (e.g. 'en' or 'en-US') |
| kind | string | Required. The kind of pusher to configure. "http" makes a pusher that sends HTTP pokes. null deletes the pusher. |
| app_display_name | string | Required. A string that will allow the user to identify what application owns this pusher. |
| device_display_name | string | Required. A string that will allow the user to identify what device owns this pusher. |
| app_id | string | Required. This is a reverse-DNS style identifier for the application. It is recommended that this end with the platform, such that different platform versions get different app identifiers. Max length, 64 chars. |
| profile_tag | string | Required. This is a string that determines what set of device rules will be matched when evaluating push rules for this pusher. It is an arbitrary string. Multiple devices may use the same profile_tag. It is advised that when an app's data is copied or restored to a different device, this value remain the same. Client apps should offer ways to change the profile_tag, optionally copying rules from the old profile tag. Max length, 32 bytes. |
| pushkey | string | Required. This is a unique identifier for this pusher. The value you should use for this is the routing or destination address information for the notification, for example, the APNS token for APNS or the Registration ID for GCM. If your notification client has no such concept, use any unique identifier. Max length, 512 bytes. |
| data | object | Required. A dictionary of information for the pusher implementation itself. If kind is http, this should contain url which is the URL to use to send notifications to. |
| data.url | string | Required if kind is http. The URL to use to send notifications to. |
| append | boolean | If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with different user IDs. Otherwise, the Home Server must remove any other pushers with the same App ID and pushkey for different users. The default is false. |
Example request:
POST /_matrix/client/api/v1/pushers/set HTTP/1.1 Content-Type: application/json { "lang": "en", "kind": "http", "app_display_name": "Mat Rix", "device_display_name": "iPhone 9", "app_id": "com.example.app.ios", "profile_tag": "4bea66906d0111e59d70feff819cdc9f", "pushkey": "APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ", "data": { "url": "https://push-gateway.location.here" }, "append": false }
Responses:
Status code 200:
The pusher was set.
Example
{}
Status code 400:
One or more of the pusher values were invalid.
Example
{ "error": "Missing parameters: lang, data", "errcode": "M_MISSING_PARAM" }
A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented. There are different "kinds" of push rules and each rule has an associated priority. Every push rule MUST have a kind and rule_id. The rule_id is a unique string within the kind of rule and its' scope: rule_ids do not need to be unique between rules of the same kind on different devices. Rules may have extra keys depending on the value of kind.The different kinds of rule in descending order of priority are:
Push rules may be either global or device-specific. Device specific rules only affect delivery of notifications via pushers with a matching profile_tag. All device-specific rules have a higher priority than global rules. This means that the full list of rule kinds, in descending priority order, is as follows:
Rules with the same kind can specify an ordering priority. This determines which rule is selected in the event of multiple matches. For example, a rule matching "tea" and a separate rule matching "time" would both match the sentence "It's time for tea". The ordering of the rules would then resolve the tiebreak to determine which rule is executed. Only actions for highest priority rule will be sent to the Push Gateway.
Each rule can be enabled or disabled. Disabled rules never match. If no rules match an event, the homeserver MUST NOT notify the Push Gateway for that event. Homeservers MUST NOT notify the Push Gateway for events that the user has sent themselves.
All rules have an associated list of actions. An action affects if and how a notification is delivered for a matching event. The following actions are defined:
Actions that have no parameters are represented as a string. Otherwise, they are represented as a dictionary with a key equal to their name and other keys as their parameters, e.g. { "set_tweak": "sound", "value": "default" }
The set_tweak action is used to add an entry to the 'tweaks' dictionary that is sent in the notification request to the Push Gateway. The following tweaks are defined:
Tweaks are passed transparently through the homeserver so client applications and Push Gateways may agree on additional tweaks. For example, a tweak may be added to specify how to flash the notification light on a mobile device.
Homeservers can specify "server-default rules" which operate at a lower priority than "user-defined rules". The rule_id for all server-default rules MUST start with a dot (".") to identify them as "server-default". The following server-default rules are specified:
Matches any message whose content is unencrypted and contains the local part of the user's Matrix ID, separated by word boundaries.
Definition (as a content rule):
{ "rule_id": ".m.rule.contains_user_name" "pattern": "[the local part of the user's Matrix ID]", "actions": [ "notify", { "set_tweak": "sound", "value": "default" } ], }
Matches any message whose content is unencrypted and contains the user's current display name in the room in which it was sent.
Definition (this rule can only be an override or underride rule):
{ "rule_id": ".m.rule.contains_display_name" "conditions": [ { "kind": "contains_display_name" } ], "actions": [ "notify", { "set_tweak": "sound", "value": "default" } ], }
Matches any message sent in a room with exactly two members.
Definition (this rule can only be an override or underride rule):
{ "rule_id": ".m.rule.room_two_members" "conditions": [ { "is": "2", "kind": "room_member_count" } ], "actions": [ "notify", { "set_tweak": "sound", "value": "default" } ], }
Matches messages with a msgtype of notice. This should be an override rule so that it takes priority over content / sender / room rules.
Definition:
{ 'rule_id': '.m.rule.suppress_notices', 'conditions': [ { 'kind': 'event_match', 'key': 'content.msgtype', 'pattern': 'm.notice', } ], 'actions': [ 'dont-notify', ] }
Matches any message. Used to define the behaviour of messages that match no other rules. If homeservers define this it should be the lowest priority underride rule.
Definition:
{ "rule_id": ".m.rule.fallback" "conditions": [], "actions": [ "notify" ], }
Override, Underride and Default Rules MAY have a list of 'conditions'. All conditions must hold true for an event in order to apply the action for the event. A rule with no conditions always matches. Room, Sender, User and Content rules do not have conditions in the same way, but instead have predefined conditions. These conditions can be configured using the parameters outlined below. In the cases of room and sender rules, the rule_id of the rule determines its behaviour. The following conditions are defined:
This is a glob pattern match on a field of the event. Parameters:
Matches the profile_tag of the device that the notification would be delivered to. Parameters:
This matches the current number of members in the room. Parameters:
Clients can retrieve, add, modify and remove push rules globally or per-device using the APIs below.
Retrieve all push rulesets for this user. Clients can "drill-down" on the rulesets by suffixing a scope to this path e.g. /pushrules/global/. This will return a subset of this data under the specified key e.g. the global key.
| Requires auth: | Yes. |
|---|
Request format:
No parameters
Response format:
| Parameter | Type | Description |
|---|---|---|
| device | {$PROFILE_TAG: Ruleset} | A dictionary of profile tags to rulesets. |
| global | {Ruleset} | The global ruleset. |
Ruleset
| Parameter | Type | Description |
|---|---|---|
| content | [PushRule] | |
| override | [PushRule] | |
| room | [PushRule] | |
| sender | [PushRule] | |
| underride | [PushRule] |
PushRule
| Parameter | Type | Description |
|---|---|---|
| actions | [object or string] | |
| default | boolean | |
| enabled | boolean | |
| rule_id | string |
Example request:
GET /_matrix/client/api/v1/pushrules/ HTTP/1.1
Response:
Status code 200:
All the push rulesets for this user.
Example
{ "device": {}, "global": { "content": [ { "actions": [ "notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" } ], "default": true, "enabled": true, "pattern": "alice", "rule_id": ".m.rule.contains_user_name" } ], "override": [ { "actions": [ "dont_notify" ], "conditions": [], "default": true, "enabled": false, "rule_id": ".m.rule.master" }, { "actions": [ "dont_notify" ], "conditions": [ { "key": "content.msgtype", "kind": "event_match", "pattern": "m.notice" } ], "default": true, "enabled": true, "rule_id": ".m.rule.suppress_notices" } ], "room": [], "sender": [], "underride": [ { "actions": [ "notify", { "set_tweak": "sound", "value": "ring" }, { "set_tweak": "highlight", "value": false } ], "conditions": [ { "key": "type", "kind": "event_match", "pattern": "m.call.invite" } ], "default": true, "enabled": true, "rule_id": ".m.rule.call" }, { "actions": [ "notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" } ], "conditions": [ { "kind": "contains_display_name" } ], "default": true, "enabled": true, "rule_id": ".m.rule.contains_display_name" }, { "actions": [ "notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false } ], "conditions": [ { "is": "2", "kind": "room_member_count" } ], "default": true, "enabled": true, "rule_id": ".m.rule.room_one_to_one" }, { "actions": [ "notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false } ], "conditions": [ { "key": "type", "kind": "event_match", "pattern": "m.room.member" }, { "key": "content.membership", "kind": "event_match", "pattern": "invite" }, { "key": "state_key", "kind": "event_match", "pattern": "@alice:example.com" } ], "default": true, "enabled": true, "rule_id": ".m.rule.invite_for_me" }, { "actions": [ "notify", { "set_tweak": "highlight", "value": false } ], "conditions": [ { "key": "type", "kind": "event_match", "pattern": "m.room.member" } ], "default": true, "enabled": true, "rule_id": ".m.rule.member_event" }, { "actions": [ "notify", { "set_tweak": "highlight", "value": false } ], "conditions": [ { "key": "type", "kind": "event_match", "pattern": "m.room.message" } ], "default": true, "enabled": true, "rule_id": ".m.rule.message" } ] } }
This endpoint allows the creation, modification and deletion of pushers for this user ID. The behaviour of this endpoint varies depending on the values in the JSON body.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| scope | string | Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag. |
| kind | enum | Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"] |
| ruleId | string | Required. The identifier for the rule. |
| JSON body parameters | ||
| conditions | array[object] | The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches. |
| conditions[0].kind | enum | One of: ["event_match", "profile_tag", "contains_display_name", "room_member_count"] |
| actions | array[string] | Required. The action(s) to perform when the conditions for this rule are met. |
| query parameters | ||
| before | string | Use 'before' with a rule_id as its value to make the new rule the next-most important rule with respect to the given rule. |
| after | string | This makes the new rule the next-less important rule relative to the given rule. |
Example request:
PUT /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com?before=someRuleId&after=anotherRuleId HTTP/1.1 Content-Type: application/json { "pattern": "cake*lie", "actions": ["notify"] }
Responses:
Status code 200:
The pusher was set.
Example
{}
Status code 400:
There was a problem configuring this push rule.
Example
{ "error": "before/after rule not found: someRuleId", "errcode": "M_UNKNOWN" }
Retrieve a single specified push rule.
| Requires auth: | Yes. |
|---|
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| scope | string | Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag. |
| kind | enum | Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"] |
| ruleId | string | Required. The identifier for the rule. |
Example request:
GET /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com HTTP/1.1
Response:
Status code 200:
The specific push rule. This will also include keys specific to the rule itself such as the rule's actions and conditions if set.
Example
{ "actions": [ "dont_notify" ], "rule_id": "#spam:matrix.org", "enabled": true }
This endpoint removes the push rule defined in the path.
| Requires auth: | Yes. |
|---|
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| scope | string | Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag. |
| kind | enum | Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"] |
| ruleId | string | Required. The identifier for the rule. |
Example request:
DELETE /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com HTTP/1.1
Response:
Status code 200:
The push rule was deleted.
Example
{}
This endpoint allows clients to enable or disable the specified push rule.
| Requires auth: | Yes. |
|---|
Request format:
| Parameter | Type | Description |
|---|---|---|
| body parameters | ||
| <body> | boolean | Required. Whether the push rule is enabled or not. |
| path parameters | ||
| scope | string | Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag. |
| kind | enum | Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"] |
| ruleId | string | Required. The identifier for the rule. |
Example request:
PUT /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com/enabled HTTP/1.1 Content-Type: application/json true
Response:
Status code 200:
The push rule was enabled or disabled.
Example
{}
To create a rule that suppresses notifications for the room with ID !dj234r78wl45Gh4D:matrix.org:
curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/room/%21dj234r78wl45Gh4D%3Amatrix.org?access_token=123456" -d \ '{ "actions" : ["dont_notify"] }'
To suppress notifications for the user @spambot:matrix.org:
curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/sender/%40spambot%3Amatrix.org?access_token=123456" -d \ '{ "actions" : ["dont_notify"] }'
To always notify for messages that contain the work 'cake' and set a specific sound (with a rule_id of SSByZWFsbHkgbGlrZSBjYWtl):
curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/content/SSByZWFsbHkgbGlrZSBjYWtl?access_token=123456" -d \ '{ "pattern": "cake", "actions" : ["notify", {"set_sound":"cakealarm.wav"}] }'
To add a rule suppressing notifications for messages starting with 'cake' but ending with 'lie', superseding the previous rule:
curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/content/U3BvbmdlIGNha2UgaXMgYmVzdA?access_token=123456&before=SSByZWFsbHkgbGlrZSBjYWtl" -d \ '{ "pattern": "cake*lie", "actions" : ["notify"] }'
To add a custom sound for notifications messages containing the word 'beer' in any rooms with 10 members or fewer (with greater importance than the room, sender and content rules):
curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/override/U2VlIHlvdSBpbiBUaGUgRHVrZQ?access_token=123456" -d \ '{ "conditions": [ {"kind": "event_match", "key": "content.body", "pattern": "beer" }, {"kind": "room_member_count", "is": "<=10"} ], "actions" : [ "notify", {"set_sound":"beeroclock.wav"} ] }'
This describes the format used by "HTTP" pushers to send notifications of events to Push Gateways. If the endpoint returns an HTTP error code, the homeserver SHOULD retry for a reasonable amount of time using exponential-backoff.
This endpoint is invoked by HTTP pushers to notify a push gateway about an event. NB: Notifications are sent to the URL configured when the pusher is created. This means that the HTTP path may be different depending on the push gateway.
Request format:
| Parameter | Type | Description |
|---|---|---|
| JSON body parameters | ||
| notification | object | Required. Information about the push notification |
| notification.content | {EventContent} | The content field from the event, if present. If the event had no content field, this field is omitted. |
| notification.id | string | Required. An identifier for this notification that may be used to detect duplicate notification requests. This is not necessarily the ID of the event that triggered the notification. |
| notification.prio | enum | The priority of the notification. If omitted, high is assumed. This may be used by push gateways to deliver less time-sensitive notifications in a way that will preserve battery power on mobile devices. One of: ["high", "low"] |
| notification.room_alias | string | An alias to display for the room in which the event occurred. |
| notification.room_id | string | Required. The ID of the room in which this event occurred. |
| notification.room_name | string | The name of the room in which the event occurred. |
| notification.sender | string | Required. The sender of the event as in the corresponding event field. |
| notification.sender_display_name | string | The current display name of the sender in the room in which the event occurred. |
| notification.type | string | Required. The type of the event as in the event's type field. |
| notification.user_is_target | boolean | This is true if the user receiving the notification is the subject of a member event (i.e. the state_key of the member event is equal to the user's Matrix ID). |
| counts | object | Required. This is a dictionary of the current number of unacknowledged communications for the recipient user. Counts whose value is zero are omitted. |
| counts.missed_calls | integer | The number of unacknowledged missed calls a user has across all rooms of which they are a member. |
| counts.unread | integer | The number of unread messages a user has across all of the rooms they are a member of. |
| devices | array[object] | Required. This is an array of devices that the notification should be sent to. |
| devices[0].app_id | string | The app_id given when the pusher was created. |
| devices[0].data | {PusherData} | A dictionary of additional pusher-specific data. For 'http' pushers, this is the data dictionary passed in at pusher creation minus the url key. |
| devices[0].pushkey | string | The pushkey given when the pusher was created. |
| devices[0].pushkey_ts | integer | The unix timestamp (in seconds) when the pushkey was last updated. |
| devices[0].tweaks | {Tweaks} | A dictionary of customisations made to the way this notification is to be presented. These are added by push rules. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| rejected | [string] | A list of all pushkeys given in the notification request that are not valid. These could have been rejected by an upstream gateway because they have expired or have never been valid. Homeservers must cease sending notification requests for these pushkeys and remove the associated pushers. It may not necessarily be the notification in the request that failed: it could be that a previous notification to the same pushkey failed. |
Example request:
POST /_matrix/push/v1/notify HTTP/1.1 Content-Type: application/json { "notification": { "id": "$3957tyerfgewrf384", "room_id": "!slw48wfj34rtnrf:example.com", "type": "m.room.message", "sender": "@exampleuser:matrix.org", "sender_display_name": "Major Tom", "room_name": "Mission Control", "room_alias": "#exampleroom:matrix.org", "prio": "high", "content": { "msgtype": "m.text", "body": "I'm floating in a most peculiar way." } }, "counts": { "unread" : 2, "missed_calls": 1 }, "devices": [ { "app_id": "org.matrix.matrixConsole.ios", "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", "pushkey_ts": 12345678, "data" : {}, "tweaks": { "sound": "bing" } } ] }
Response:
Status code 200:
A list of rejected push keys.
Example
{ "rejected": [ "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/" ] }
The exact format for sending APNS notifications is flexible and up to the client app and its' push gateway to agree on. As APNS requires that the sender has a private key owned by the app developer, each app must have its own push gateway. It is recommended that:
Clients specify the Push Gateway URL to use to send event notifications to. This URL should be over HTTPS and never over HTTP.
As push notifications will pass through a Push Provider, message content shouldn't be sent in the push itself where possible. Instead, Push Gateways should send a "sync" command to instruct the client to get new events from the homeserver directly.
This module adds in support for inviting new members to a room where their Matrix user ID is not known, instead addressing them by a third party identifier such as an email address. There are two flows here; one if a Matrix user ID is known for the third party identifier, and one if not. Either way, the client calls /invite with the details of the third party identifier.
The homeserver asks the identity server whether a Matrix user ID is known for that identifier:
When the invitee's homeserver receives the notification of the binding, it should insert an m.room.member event into the room's graph for that user, with content.membership = invite, as well as a content.third_party_invite property which contains proof that the invitee does indeed own that third party identifier.
Acts as an m.room.member invite event, where there isn't a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room.
| Content Key | Type | Description |
|---|---|---|
| display_name | string | A user-readable string which represents the user who has been invited. This should not contain the user's third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID. |
| key_validity_url | string | A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'. |
| public_key | string | A base64-encoded ed25519 key with which token must be signed. |
Example:
{ "age": 242352, "content": { "display_name": "Alice Margatroid", "key_validity_url": "https://magic.forest/verifykey", "public_key": "abc123" }, "event_id": "$WLGTSEFSEF:localhost", "origin_server_ts":<PHONE_NUMBER>939, "room_id": "!Cuyf34gef24t:localhost", "sender": "@example:localhost", "state_key": "pc98", "type": "m.room.third_party_invite" }
A client asks a server to invite a user by their third party identifier.
Note that there are two forms of this API, which are documented separately. This version of the API does not require that the inviter know the Matrix identifier of the invitee, and instead relies on third party identifiers. The homeserver uses an identity server to perform the mapping from third party identifier to a Matrix identifier. The other is documented in the joining rooms section.
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
Only users currently in a particular room can invite other users to join that room.
If the identity server did know the Matrix user identifier for the third party identifier, the home server will append a m.room.member event to the room.
If the identity server does not know a Matrix user identifier for the passed third party identifier, the homeserver will issue an invitation which can be accepted upon providing proof of ownership of the third party identifier. This is achieved by the identity server generating a token, which it gives to the inviting homeserver. The homeserver will add an m.room.third_party_invite event into the graph for the room, containing that token.
When the invitee binds the invited third party identifier to a Matrix user ID, the identity server will give the user a list of pending invitations, each containing:
If a token is requested from the identity server, the home server will append a m.room.third_party_invite event to the room.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| roomId | string | Required. The room identifier (not alias) to which to invite the user. |
| JSON body parameters | ||
| id_server | string | Required. The hostname+port of the identity server which should be used for third party identifier lookups. |
| medium | string | Required. The kind of address being passed in the address field, for example email. |
| address | string | Required. The invitee's third party identifier. |
Example request:
POST /_matrix/client/api/v1/rooms/%21d41d8cd%3Amatrix.org/invite HTTP/1.1 Content-Type: application/json { "id_server": "matrix.org", "medium": "email", "address"<EMAIL_ADDRESS>}
Responses:
Status code 200:
The user has been invited to join the room.
Example
{}
Status code 403:
You do not have permission to invite the user to the room. A meaningful errcode and description error text will be returned. Example reasons for rejections are:
Example
{"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"}
All homeservers MUST verify the signature in the event's content.third_party_invite.signed object.
When a homeserver inserts an m.room.member invite event into the graph because of an m.room.third_party_invite event, that homesever MUST validate that the public key used for signing is still valid, by checking key_validity_url from the m.room.third_party_invite. It does this by making an HTTP GET request to key_validity_url:
Schema:
=> GET $key_validity_url?public_key=$public_key <= HTTP/1.1 200 OK { "valid": true|false }
Example:
key_validity_url = https://identity.server/is_valid public_key = ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4 => GET https://identity.server/is_valid?public_key=ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4 <= HTTP/1.1 200 OK { "valid": true }
with the querystring ?public_key=``public_key``. A JSON object will be returned. The invitation is valid if the object contains a key named valid which is true. Otherwise, the invitation MUST be rejected. This request is idempotent and may be retried by the homeserver.
If a homeserver is joining a room for the first time because of an m.room.third_party_invite, the server which is already participating in the room (which is chosen as per the standard server-server specification) MUST validate that the public key used for signing is still valid, by checking key_validity_url in the above described way.
No other homeservers may reject the joining of the room on the basis of key_validity_url, this is so that all homeservers have a consistent view of the room. They may, however, indicate to their clients that a member's' membership is questionable.
For example:
The reason that no other homeserver may reject the event based on checking key_validity_url is that we must ensure event acceptance is deterministic. If some other participating server doesn't have a network path to the keyserver, or if the keyserver were to go offline, or revoke its keys, that other server would reject the event and cause the participating servers' graphs to diverge. This relies on participating servers trusting each other, but that trust is already implied by the server-server protocol. Also, the public key signature verification must still be performed, so the attack surface here is minimized.
There are a number of privary and trust implications to this module.
It is important for user privacy that leaking the mapping between a matrix user ID and a third party identifier is hard. In particular, being able to look up all third party identifiers from a matrix user ID (and accordingly, being able to link each third party identifier) should be avoided wherever possible. To this end, the third party identifier is not put in any event, rather an opaque display name provided by the identity server is put into the events. Clients should not remember or display third party identifiers from invites, other than for the use of the inviter themself.
Homeservers are not required to trust any particular identity server(s). It is generally a client's responsibility to decide which identity servers it trusts, not a homeserver's. Accordingly, this API takes identity servers as input from end users, and doesn't have any specific trusted set. It is possible some homeservers may want to supply defaults, or reject some identity servers for its users, but no homeserver is allowed to dictate which identity servers other homeservers' users trust.
There is some risk of denial of service attacks by flooding homeservers or identity servers with many requests, or much state to store. Defending against these is left to the implementer's discretion.
The search API allows clients to perform full text search across events in all rooms that the user has been in, including those that they have left. Only events that the user is allowed to see will be searched, e.g. it won't include events in rooms that happened after you left.
Performs a full text search across different categories.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| JSON body parameters | ||
| search_categories | object | Required. Describes which categories to search in and their criteria. |
| search_categories.room_events | {Room Events} | Mapping of category name to search criteria. |
| search_categories.room_events.filter | {Filter} | The filter to apply to search results. This has the same format as v2 filter API. |
| search_categories.room_events.keys | [enum] | The keys to search. Defaults to all. One of: ["content.body", "content.name", "content.topic"] |
| search_categories.room_events.search_term | string | Required. The string to search events for |
Response format:
Results
| Parameter | Type | Description |
|---|---|---|
| search_categories | {Categories} | Describes which categories to search in and their criteria. |
Categories
| Parameter | Type | Description |
|---|---|---|
| room_events | {Room Event Results} | Mapping of category name to search criteria. |
Room Event Results
| Parameter | Type | Description |
|---|---|---|
| count | number | Total number of results found |
| results | {string: Result} | Mapping of event_id to result. |
Result
| Parameter | Type | Description |
|---|---|---|
| rank | number | A number that describes how closely this result matches the search. Higher is closer. |
| result | {Event} | The event that matched. |
Event
| Parameter | Type | Description |
|---|---|---|
| event_id | string | The globally unique event identifier. |
| room_id | string | The ID of the room associated with this event. |
| user_id | string | Contains the fully-qualified ID of the user who sent this event. |
Example request:
POST /_matrix/client/api/v1/search HTTP/1.1 Content-Type: application/json { "search_categories": { "room_events": { "keys": [ "content.body" ], "search_term": "martians and men" } } }
Response:
Status code 200:
Results of the search.
Example
{ "search_categories": { "room_events": { "count": 24, "results": { "$144429830826TWwbB:localhost": { "rank": 0.00424866, "result": { "age": 526228296, "content": { "body": "Test content", "msgtype": "m.text" }, "event_id": "$144429830826TWwbB:localhost", "origin_server_ts":<PHONE_NUMBER>034, "room_id": "!qPewotXpIctQySfjSy:localhost", "type": "m.room.message", "user_id": "@test:localhost" } } } } } }
The search API allows clients to search in different categories of items. Currently the only specified category is room_events.
This category covers all events that the user is allowed to see, including events in rooms that they have left. The search is performed on certain keys of certain event types.
The supported keys to search over are:
The search will not include rooms that are end to end encrypted.
The results include a rank key that can be used to sort the results by revelancy. The higher the rank the more relevant the result is.
The value of count may not match the number of results. For example due to the search query matching 1000s of results and the server truncating the response.
The server must only return results that the user has permission to see.
There are times when it is desirable for clients to be able to interact with rooms without having to fully register for an account on a homeserver or join the room. This module specifies how these clients should interact with servers in order to participate in rooms as guests.
Guest users retrieve access tokens from a homeserver using the ordinary register endpoint, specifying the kind parameter as guest. They may then interact with the client-server API as any other user would, but will only have access to a subset of the API as described the Client behaviour subsection below. Homeservers may choose not to allow this access at all to their local users, but have no information about whether users on other homeservers are guests or not.
This module does not fully factor in federation; it relies on individual homeservers properly adhering to the rules set out in this module, rather than allowing all homeservers to enforce the rules on each other.
This event controls whether guest users are allowed to join rooms. If this event is absent, servers should act as if it is present and has the guest_access value "forbidden".
| Content Key | Type | Description |
|---|---|---|
| guest_access | enum | Whether guests can join the room. One of: ["can_join", "forbidden"] |
Example:
{ "age": 242353, "content": { "guest_access": "can_join" }, "event_id": "$WLGTSEFSEG:localhost", "origin_server_ts":<PHONE_NUMBER>938, "room_id": "!Cuyf34gef24u:localhost", "state_key": "", "type": "m.room.guest_access", "user_id": "@example:localhost" }
The following API endpoints are allowed to be accessed by guest accounts for retrieving events:
There is also a special version of the GET /events endpoint:
This will listen for new events related to a particular room and return them to the caller. This will block until an event is received, or until the timeout is reached.
This API is the same as the non-guest /events endpoint, but can be called by guest users.
| Requires auth: | Yes. |
|---|
Request format:
| Parameter | Type | Description |
|---|---|---|
| query parameters | ||
| from | string | The token to stream from. This token is either from a previous request to this API or from the initial sync API. |
| timeout | integer | The maximum time in milliseconds to wait for an event. |
| room_id | array | The room IDs for which events should be returned. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| chunk | [Event] | An array of events. |
| end | string | A token which correlates to the last value in chunk. This token should be used in the next request to /events. |
| start | string | A token which correlates to the first value in chunk. This is usually the same token supplied to from=. |
Event
| Parameter | Type | Description |
|---|---|---|
| event_id | string | The globally unique event identifier. |
| room_id | string | The ID of the room associated with this event. |
| user_id | string | Contains the fully-qualified ID of the user who sent this event. |
Example request:
GET /_matrix/client/api/v1/events?from=s3456_9_0&timeout=35000&room_id=%21somewhere%3Aover&room_id=%21the%3Arainbow HTTP/1.1
Response:
Status code 200:
The events received, which may be none.
Example
{ "start": "s3456_9_0", "end": "s3457_9_0", "chunk": [ { "age": 32, "content": { "body": "incoming message", "msgtype": "m.text" }, "event_id": "$14328055551tzaee:localhost", "origin_server_ts":<PHONE_NUMBER>886, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", "user_id": "@bob:localhost" } ] }
They will only return events which happened while the room state had the m.room.history_visibility state event present with history_visibility value world_readable. Guest clients do not need to join rooms in order to receive events for them.
The following API endpoints are allowed to be accessed by guest accounts for sending events:
Guest clients do need to join rooms in order to send events to them.
The following API endpoints are allowed to be accessed by guest accounts for their own account maintenance:
Servers are required to only return events to guest accounts for rooms where the room state at the event had the m.room.history_visibility state event present with history_visibility value world_readable. These events may be returned even if the anonymous user is not joined to the room.
Servers MUST only allow guest users to join rooms if the m.room.guest_access state event is present on the room, and has the guest_access value can_join. If the m.room.guest_access event is changed to stop this from being the case, the server MUST set those users' m.room.member state to leave.
Each homeserver manages its own guest accounts itself, and whether an account is a guest account or not is not information passed from server to server. Accordingly, any server participating in a room is trusted to properly enforce the permissions outlined in this section.
Clients may wish to display to their users that rooms which are world_readable may be showing messages to non-joined users. There is no way using this module to find out whether any non-joined guest users do see events in the room, or to list or count any guest users.
Homeservers may want to enable protections such as captchas for guest registration to prevent spam, denial of service, and similar attacks.
The Matrix client-server API and server-server APIs provide the means to implement a consistent self-contained federated messaging fabric. However, they provide limited means of implementing custom server-side behaviour in Matrix (e.g. gateways, filters, extensible hooks etc). The Application Service API (AS API) defines a standard API to allow such extensible functionality to be implemented irrespective of the underlying homeserver implementation.
Application services are passive and can only observe events from a given homeserver. They can inject events into rooms they are participating in. They cannot prevent events from being sent, nor can they modify the content of the event being sent. In order to observe events from a homeserver, the homeserver needs to be configured to pass certain types of traffic to the application service. This is achieved by manually configuring the homeserver with information about the application service (AS).
Note
Previously, application services could register with a homeserver via HTTP APIs. This was removed as it was seen as a security risk. A compromised application service could re-register for a global * regex and sniff all traffic on the homeserver. To protect against this, application services now have to register via configuration files which are linked to the homeserver configuration file. The addition of configuration files allows homeserver admins to sanity check the registration for suspicious regex strings.
Application services register "namespaces" of user IDs, room aliases and room IDs. These namespaces are represented as regular expressions. An application service is said to be "interested" in a given event if one of the IDs in the event match the regular expression provided by the application service. An application service can also state whether they should be the only ones who can manage a specified namespace. This is referred to as an "exclusive" namespace. An exclusive namespace prevents humans and other application services from creating/deleting entities in that namespace. Typically, exclusive namespaces are used when the rooms represent real rooms on another service (e.g. IRC). Non-exclusive namespaces are used when the application service is merely augmenting the room itself (e.g. providing logging or searching facilities). Namespaces are represented by POSIX extended regular expressions and look like:
users: - exclusive: true regex: @irc.freenode.net_.*
The registration is represented by a series of key-value pairs, which this specification will present as YAML. An example HS configuration required to pass traffic to the AS is:
url: <base url of AS> as_token: <token AS will add to requests to HS> hs_token: <token HS will add to requests to AS> sender_localpart: <localpart of AS user> namespaces: users: # Namespaces of users which should be delegated to the AS - exclusive: <bool> regex: <regex> - ... aliases: [] # Namespaces of room aliases which should be delegated to the AS rooms: [] # Namespaces of room ids which should be delegated to the AS
Warning
If the homeserver in question has multiple application services, each as_token MUST be unique per application service as this token is used to identify the application service. The homeserver MUST enforce this.
The application service API provides a transaction API for sending a list of events. Each list of events includes a transaction ID, which works as follows:
Typical HS ---> AS : Home server sends events with transaction ID T. <--- : AS sends back 200 OK. AS ACK Lost HS ---> AS : Home server sends events with transaction ID T. <-/- : AS 200 OK is lost. HS ---> AS : Home server retries with the same transaction ID of T. <--- : AS sends back 200 OK. If the AS had processed these events already, it can NO-OP this request (and it knows if it is the same events based on the transaction ID).
The events sent to the application service should be linearised, as if they were from the event stream. The homeserver MUST maintain a queue of transactions to send to the AS. If the application service cannot be reached, the homeserver SHOULD backoff exponentially until the application service is reachable again. As application services cannot modify the events in any way, these requests can be made without blocking other aspects of the homeserver. Homeservers MUST NOT alter (e.g. add more) events they were going to send within that transaction ID on retries, as the AS may have already processed the events.
The application service API includes two querying APIs: for room aliases and for user IDs. The application service SHOULD create the queried entity if it desires. During this process, the application service is blocking the homeserver until the entity is created and configured. If the homeserver does not receive a response to this request, the homeserver should retry several times before timing out. This should result in an HTTP status 408 "Request Timeout" on the client which initiated this request (e.g. to join a room alias).
Rationale
Blocking the homeserver and expecting the application service to create the entity using the client-server API is simpler and more flexible than alternative methods such as returning an initial sync style JSON blob and get the HS to provision the room/user. This also meant that there didn't need to be a "backchannel" to inform the application service about information about the entity such as room ID to room alias mappings.
This contains application service APIs which are used by the home server. All application services MUST implement these APIs. These APIs are defined below.
This endpoint is invoked by the homeserver on an application service to query the existence of a given room alias. The homeserver will only query room aliases inside the application service's aliases namespace. The homeserver will send this request when it receives a request to join a room alias within the application service's namespace.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| roomAlias | string | Required. The room alias being queried. |
Example request:
GET /rooms/%23magicforest%3Aexample.com HTTP/1.1
Responses:
Status code 200:
The application service indicates that this room alias exists. The application service MUST have created a room and associated it with the queried room alias using the client-server API. Additional information about the room such as its name and topic can be set before responding.
Example
{}
Status code 401:
The homeserver has not supplied credentials to the application service. Optional error information can be included in the body of this response.
Example
{ "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED" }
Status code 403:
The credentials supplied by the homeserver were rejected.
Example
{ "errcode": "M_FORBIDDEN" }
Status code 404:
The application service indicates that this room alias does not exist. Optional error information can be included in the body of this response.
Example
{ "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND" }
This API is called by the HS when the HS wants to push an event (or batch of events) to the AS.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| txnId | string | Required. The transaction ID for this set of events. Homeservers generate these IDs and they are used to ensure idempotency of requests. |
| JSON body parameters | ||
| events | array[object] | Required. A list of events |
Example request:
PUT /transactions/35 HTTP/1.1 Content-Type: application/json { "events": [ { "age": 32, "content": { "body": "incoming message", "msgtype": "m.text" }, "event_id": "$14328055551tzaee:localhost", "origin_server_ts":<PHONE_NUMBER>886, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", "user_id": "@bob:localhost" }, { "age": 1984, "content": { "body": "another incoming message", "msgtype": "m.text" }, "event_id": "$1228055551ffsef:localhost", "origin_server_ts":<PHONE_NUMBER>886, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", "user_id": "@bob:localhost" } ] }
Response:
Status code 200:
The transaction was processed successfully.
Example
{}
This endpoint is invoked by the homeserver on an application service to query the existence of a given user ID. The homeserver will only query user IDs inside the application service's users namespace. The homeserver will send this request when it receives an event for an unknown user ID in the application service's namespace.
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user ID being queried. |
Example request:
GET /users/%40alice%3Aexample.com HTTP/1.1
Responses:
Status code 200:
The application service indicates that this user exists. The application service MUST create the user using the client-server API.
Example
{}
Status code 401:
The homeserver has not supplied credentials to the application service. Optional error information can be included in the body of this response.
Example
{ "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED" }
Status code 403:
The credentials supplied by the homeserver were rejected.
Example
{ "errcode": "M_FORBIDDEN" }
Status code 404:
The application service indicates that this user does not exist. Optional error information can be included in the body of this response.
Example
{ "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND" }
Application services can utilise a more powerful version of the client-server API by identifying itself as an application service to the home server.
The client-server API infers the user ID from the access_token provided in every request. It would be an annoying amount of book-keeping to maintain tokens for every virtual user. It would be preferable if the application service could use the CS API with its own as_token instead, and specify the virtual user they wish to be acting on behalf of. For real users, this would require additional permissions granting the AS permission to masquerade as a matrix user.
/path?access_token=$token&user_id=$userid Query Parameters: access_token: The application service token user_id: The desired user ID to act as.
The application service may want to inject events at a certain time (reflecting the time on the network they are tracking e.g. irc, xmpp). Application services need to be able to adjust the origin_server_ts value to do this.
/path?access_token=$token&ts=$timestamp Query Parameters added to the send event APIs only: access_token: The application service token ts: The desired timestamp
The home server needs to give the application service full control over its namespace, both for users and for room aliases. This means that the AS should be able to create/edit/delete any room alias in its namespace, as well as create/delete any user in its namespace. No additional API changes need to be made in order for control of room aliases to be granted to the AS. Creation of users needs API changes in order to:
This involves bypassing the registration flows entirely. This is achieved by including the AS token on a /register request, along with a login type of m.login.application_service to set the desired user ID without a password.
/register?access_token=$as_token Content: { type: "m.login.application_service", user: "<desired user localpart in AS namespace>" }
Application services which attempt to create users or aliases outside of their defined namespaces will receive an error code M_EXCLUSIVE. Similarly, normal users who attempt to create users or aliases inside an application service-defined namespace will receive the same M_EXCLUSIVE error code, but only if the application service has defined the namespace as exclusive.
This concerns the well-defined conventions for mapping 3P network IDs to matrix IDs, which we expect clients to be able to do by themselves.
Matrix users may wish to directly contact a virtual user, e.g. to send an email. The URI format is a well-structured way to represent a number of different ID types, including:
As a result, virtual user IDs SHOULD relate to their URI counterpart. This mapping from URI to user ID can be expressed in a number of ways:
Exposing an API would allow HSes to internally map user IDs however they like, at the cost of an extra round trip (of which the response can be cached). Munging the URI would allow clients to apply the mapping locally, but would force user X on service Y to always map to the same munged user ID. Considering the exposed API could just be applying this munging, there is more flexibility if an API is exposed.
GET /_matrix/app/v1/user?uri=$url_encoded_uri Returns 200 OK: { user_id: <complete user ID on local HS> }
We may want to expose some 3P network rooms so Matrix users can join them directly, e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. mailto, tel. Rooms which are publicly accessible (e.g. IRC rooms) can be exposed as an alias by the application service. Private rooms (e.g. sending an email to someone) should not be exposed in this way, but should instead operate using normal invite/join semantics. Therefore, the ID conventions discussed below are only valid for public rooms which expose room aliases.
Matrix users may wish to join XMPP rooms (e.g. using XEP-0045) or IRC rooms. In both cases, these rooms can be expressed as URIs. For consistency, these "room" URIs SHOULD be mapped in the same way as "user" URIs.
GET /_matrix/app/v1/alias?uri=$url_encoded_uri Returns 200 OK: { alias: <complete room alias on local HS> }
We recommend that any events that originated from a remote network should include an external_url field in their content to provide a way for Matrix clients to link into the 'native' client from which the event originated. For instance, this could contain the message-ID for emails/nntp posts, or a link to a blog comment when bridging blog comment traffic in & out of Matrix.
Matrix home servers use the Federation APIs (also known as server-server APIs) to communicate with each other. Home servers use these APIs to push messages to each other in real-time, to request historic messages from each other, and to query profile and presence information about users on each other's servers.
The APIs are implemented using HTTPS GETs and PUTs between each of the servers. These HTTPS requests are strongly authenticated using public key signatures at the TLS transport layer and using public key signatures in HTTP Authorization headers at the HTTP layer.
There are three main kinds of communication that occur between home servers:
These events are broadcast from one home server to any others that have joined the same room (identified by Room ID). They are persisted in long-term storage and record the history of messages and state for a room.
Like email, it is the responsibility of the originating server of a PDU to deliver that event to its recipient servers. However PDUs are signed using the originating server's public key so that it is possible to deliver them through third-party servers.
EDUs and PDUs are further wrapped in an envelope called a Transaction, which is transferred from the origin to the destination home server using an HTTPS PUT request.
Each matrix home server is identified by a server name consisting of a DNS name and an optional TLS port.
server_name = dns_name [ ":" tls_port] dns_name = <host, see [RFC 3986], Section 3.2.2> tls_port = *DIGIT
If the port is present then the server is discovered by looking up an AAAA or A record for the DNS name and connecting to the specified TLS port. If the port is absent then the server is discovered by looking up a _matrix._tcp SRV record for the DNS name. If this record does not exist then the server is discovered by looking up an AAAA or A record on the DNS name and taking the default fallback port number of 8448. Home servers may use SRV records to load balance requests between multiple TLS endpoints or to failover to another endpoint if an endpoint fails.
Each home server publishes its public keys under /_matrix/key/v2/server/. Home servers query for keys by either getting /_matrix/key/v2/server/ directly or by querying an intermediate notary server using a /_matrix/key/v2/query API. Intermediate notary servers query the /_matrix/key/v2/server/ API on behalf of another server and sign the response with their own key. A server may query multiple notary servers to ensure that they all report the same public keys.
This approach is borrowed from the Perspectives Project, but modified to include the NACL keys and to use JSON instead of XML. It has the advantage of avoiding a single trust-root since each server is free to pick which notary servers they trust and can corroborate the keys returned by a given notary server by querying other servers.
Home servers publish the allowed TLS fingerprints and signing keys in a JSON object at /_matrix/key/v2/server/{key_id}. The response contains a list of verify_keys that are valid for signing federation requests made by the server and for signing events. It contains a list of old_verify_keys which are only valid for signing events. Finally the response contains a list of TLS certificate fingerprints to validate any connection made to the server.
A server may have multiple keys active at a given time. A server may have any number of old keys. It is recommended that servers return a single JSON response listing all of its keys whenever any key_id is requested to reduce the number of round trips needed to discover the relevant keys for a server. However a server may return a different responses for a different key_id.
The tls_certificates contain a list of hashes of the X.509 TLS certificates currently used by the server. The list must include SHA-256 hashes for every certificate currently in use by the server. These fingerprints are valid until the millisecond POSIX timestamp in valid_until_ts.
The verify_keys can be used to sign requests and events made by the server until the millisecond POSIX timestamp in valid_until_ts. If a Home Server receives an event with a origin_server_ts after the valid_until_ts then it should request that key_id for the originating server to check whether the key has expired.
The old_verify_keys can be used to sign events with an origin_server_ts before the expired_ts. The expired_ts is a millisecond POSIX timestamp of when the originating server stopped using that key.
Intermediate notary servers should cache a response for half of its remaining life time to avoid serving a stale response. Originating servers should avoid returning responses that expire in less than an hour to avoid repeated requests for an about to expire certificate. Requesting servers should limit how frequently they query for certificates to avoid flooding a server with requests.
If a server goes offline intermediate notary servers should continue to return the last response they received from that server so that the signatures of old events sent by that server can still be checked.
| Key | Type | Description |
|---|---|---|
| server_name | String | DNS name of the home server. |
| verify_keys | Object | Public keys of the home server for verifying digital signatures. |
| old_verify_keys | Object | The public keys that the server used to use and when it stopped using them. |
| signatures | Object | Digital signatures for this object signed using the verify_keys. |
| tls_fingerprints | Array of Objects | Hashes of X.509 TLS certificates used by this this server encoded as base64. |
| valid_until_ts | Integer | POSIX timestamp when the list of valid keys should be refreshed. |
{ "old_verify_keys": { "ed25519:auto1": { "expired_ts":<PHONE_NUMBER>00, "key": "Base+64+Encoded+Old+Verify+Key" } }, "server_name": "example.org", "signatures": { "example.org": { "ed25519:auto2": "Base+64+Encoded+Signature" } }, "tls_fingerprints": [ { "sha256": "Base+64+Encoded+SHA-256-Fingerprint" } ], "valid_until_ts":<PHONE_NUMBER>000, "verify_keys": { "ed25519:auto2": { "key": "Base+64+Encoded+Signature+Verification+Key" } } }
Servers may offer a query API _matrix/key/v2/query/ for getting the keys for another server. This API can be used to GET at list of JSON objects for a given server or to POST a bulk query for a number of keys from a number of servers. Either way the response is a list of JSON objects containing the JSON published by the server under _matrix/key/v2/server/ signed by both the originating server and by this server.
The minimum_valid_until_ts is a millisecond POSIX timestamp indicating when the returned certificate will need to be valid until to be useful to the requesting server. This can be set using the maximum origin_server_ts of an batch of events that a requesting server is trying to validate. This allows an intermediate notary server to give a prompt cached response even if the originating server is offline.
This API can return keys for servers that are offline be using cached responses taken from when the server was online. Keys can be queried from multiple servers to mitigate against DNS spoofing.
Requests:
GET /_matrix/key/v2/query/${server_name}/${key_id}/?minimum_valid_until_ts=${minimum_valid_until_ts} HTTP/1.1 POST /_matrix/key/v2/query HTTP/1.1 Content-Type: application/json { "server_keys": { "$server_name": { "$key_id": { "minimum_valid_until_ts": $posix_timestamp } } } }
Response:
HTTP/1.1 200 OK Content-Type: application/json { "server_keys": [ # List of responses with same format as /_matrix/key/v2/server # signed by both the originating server and this server. ] }
Warning
Version 1 of key distribution is obsolete
Home servers publish their TLS certificates and signing keys in a JSON object at /_matrix/key/v1.
| Key | Type | Description |
|---|---|---|
| server_name | String | DNS name of the home server. |
| verify_keys | Object | Public keys of the home server for verifying digital signatures. |
| signatures | Object | Digital signatures for this object signed using the verify_keys. |
| tls_certificate | String | The X.509 TLS certificate used by this this server encoded as base64. |
{ "server_name": "example.org", "signatures": { "example.org": { "ed25519:auto": "Base+64+Encoded+Signature" } }, "tls_certificate": "Base+64+Encoded+DER+Encoded+X509+TLS+Certificate" "verify_keys": { "ed25519:auto": "Base+64+Encoded+Signature+Verification+Key" } }
When fetching the keys for a server the client should check that the TLS certificate in the JSON matches the TLS server certificate for the connection and should check that the JSON signatures are correct for the supplied verify_keys
Warning
This section may be misleading or inaccurate.
The transfer of EDUs and PDUs between home servers is performed by an exchange of Transaction messages, which are encoded as JSON objects, passed over an HTTP PUT request. A Transaction is meaningful only to the pair of home servers that exchanged it; they are not globally-meaningful.
| Key | Type | Description |
|---|---|---|
| origin | String | DNS name of homeserver making this transaction. |
| origin_server_ts | Integer | Timestamp in milliseconds on originating homeserver when this transaction started. |
| previous_ids | List of Strings | List of transactions that were sent immediately prior to this transaction. |
| pdus | List of Objects | List of persistent updates to rooms. |
| edus | List of Objects | List of ephemeral messages. |
{ "transaction_id":"916d630ea616342b42e98a3be0b74113", "ts":1404835423000, "origin":"red", "prev_ids":["e1da392e61898be4d2009b9fecce5325"], "pdus":[...], "edus":[...] }
The prev_ids field contains a list of previous transaction IDs that the origin server has sent to this destination. Its purpose is to act as a sequence checking mechanism - the destination server can check whether it has successfully received that Transaction, or ask for a re-transmission if not.
The pdus field of a transaction is a list, containing zero or more PDUs.[*] Each PDU is itself a JSON object containing a number of keys, the exact details of which will vary depending on the type of PDU. Similarly, the edus field is another list containing the EDUs. This key may be entirely absent if there are no EDUs to transfer.
(* Normally the PDU list will be non-empty, but the server should cope with receiving an "empty" transaction, as this is useful for informing peers of other transaction IDs they should be aware of. This effectively acts as a push mechanism to encourage peers to continue to replicate content.)
All PDUs have:
| Key | Type | Description |
|---|---|---|
| context | String | Room identifier |
| user_id | String | The ID of the user sending the PDU |
| origin | String | DNS name of homeserver that created this PDU |
| pdu_id | String | Unique identifier for PDU on the originating homeserver |
| origin_server_ts | Integer | Timestamp in milliseconds on origin homeserver when this PDU was created. |
| pdu_type | String | PDU event type |
| content | Object | The content of the PDU. |
| prev_pdus | List of (String, String, Object) Triplets | The originating homeserver, PDU ids and hashes of the most recent PDUs the homeserver was aware of for the room when it made this PDU |
| depth | Integer | The maximum depth of the previous PDUs plus one |
| is_state | Boolean | True if this PDU is updating room state |
{ "context":"#example:green.example.com", "origin":"green.example.com", "pdu_id":"a4ecee13e2accdadf56c1025af232176", "origin_server_ts":1404838188000, "pdu_type":"m.room.message", "prev_pdus":[ ["blue.example.com","99d16afbc8", {"sha256":"abase64encodedsha256hashshouldbe43byteslong"}] ], "hashes":{"sha256":"thishashcoversallfieldsincasethisisredacted"}, "signatures":{ "green.example.com":{ "ed25519:key_version:":"these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus" } }, "is_state":false, "content": {...} }
In contrast to Transactions, it is important to note that the prev_pdus field of a PDU refers to PDUs that any origin server has sent, rather than previous IDs that this origin has sent. This list may refer to other PDUs sent by the same origin as the current one, or other origins.
Because of the distributed nature of participants in a Matrix conversation, it is impossible to establish a globally-consistent total ordering on the events. However, by annotating each outbound PDU at its origin with IDs of other PDUs it has received, a partial ordering can be constructed allowing causality relationships to be preserved. A client can then display these messages to the end-user in some order consistent with their content and ensure that no message that is semantically in reply of an earlier one is ever displayed before it.
PDUs fall into two main categories: those that deliver Events, and those that synchronise State. For PDUs that relate to State synchronisation, additional keys exist to support this:
| Key | Type | Description |
|---|---|---|
| state_key | String | Combined with the pdu_type this identifies the which part of the room state is updated |
| required_power_level | Integer | The required power level needed to replace this update. |
| prev_state_id | String | The homeserver of the update this replaces |
| prev_state_origin | String | The PDU id of the update this replaces. |
| user_id | String | The user updating the state. |
{..., "is_state":true, "state_key":TODO-doc "required_power_level":TODO-doc "prev_state_id":TODO-doc "prev_state_origin":TODO-doc }
EDUs, by comparison to PDUs, do not have an ID, a room ID, or a list of "previous" IDs. The only mandatory fields for these are the type, origin and destination home server names, and the actual nested content.
| Key | Type | Description |
|---|---|---|
| edu_type | String | The type of the ephemeral message. |
| content | Object | Content of the ephemeral message. |
{ "edu_type":"m.presence", "origin":"blue", "destination":"orange", "content":{...} }
Warning
This section may be misleading or inaccurate.
All these URLs are name-spaced within a prefix of:
/_matrix/federation/v1/...
For active pushing of messages representing live activity "as it happens":
PUT .../send/<transaction_id>/ Body: JSON encoding of a single Transaction Response: TODO-doc
The transaction_id path argument will override any ID given in the JSON body. The destination name will be set to that of the receiving server itself. Each embedded PDU in the transaction body will be processed.
To fetch a particular PDU:
GET .../pdu/<origin>/<pdu_id>/ Response: JSON encoding of a single Transaction containing one PDU
Retrieves a given PDU from the server. The response will contain a single new Transaction, inside which will be the requested PDU.
To fetch all the state of a given room:
GET .../state/<room_id>/ Response: JSON encoding of a single Transaction containing multiple PDUs
Retrieves a snapshot of the entire current state of the given room. The response will contain a single Transaction, inside which will be a list of PDUs that encode the state.
To backfill events on a given room:
GET .../backfill/<room_id>/ Query args: v, limit Response: JSON encoding of a single Transaction containing multiple PDUs
Retrieves a sliding-window history of previous PDUs that occurred on the given room. Starting from the PDU ID(s) given in the "v" argument, the PDUs that preceded it are retrieved, up to a total number given by the "limit" argument. These are then returned in a new Transaction containing all of the PDUs.
To stream events all the events:
GET .../pull/ Query args: origin, v Response: JSON encoding of a single Transaction consisting of multiple PDUs
Retrieves all of the transactions later than any version given by the "v" arguments.
To make a query:
GET .../query/<query_type> Query args: as specified by the individual query types Response: JSON encoding of a response object
Performs a single query request on the receiving home server. The Query Type part of the path specifies the kind of query being made, and its query arguments have a meaning specific to that kind of query. The response is a JSON-encoded object whose meaning also depends on the kind of query.
To join a room:
GET .../make_join/<room_id>/<user_id> Response: JSON encoding of a join proto-event PUT .../send_join/<room_id>/<event_id> Response: JSON encoding of the state of the room at the time of the event
Performs the room join handshake. For more information, see "Joining Rooms" below.
When a new user wishes to join room that the user's homeserver already knows about, the homeserver can immediately determine if this is allowable by inspecting the state of the room, and if it is acceptable, it can generate, sign, and emit a new m.room.member state event adding the user into that room. When the homeserver does not yet know about the room it cannot do this directly. Instead, it must take a longer multi-stage handshaking process by which it first selects a remote homeserver which is already participating in that room, and uses it to assist in the joining process. This is the remote join handshake.
This handshake involves the homeserver of the new member wishing to join (referred to here as the "joining" server), the directory server hosting the room alias the user is requesting to join with, and a homeserver where existing room members are already present (referred to as the "resident" server).
In summary, the remote join handshake consists of the joining server querying the directory server for information about the room alias; receiving a room ID and a list of join candidates. The joining server then requests information about the room from one of the residents. It uses this information to construct a m.room.member event which it finally sends to a resident server.
Conceptually these are three different roles of homeserver. In practice the directory server is likely to be resident in the room, and so may be selected by the joining server to be the assisting resident. Likewise, it is likely that the joining server picks the same candidate resident for both phases of event construction, though in principle any valid candidate may be used at each time. Thus, any join handshake can potentially involve anywhere from two to four homeservers, though most in practice will use just two.
Client Joining Directory Resident Server Server Server join request --> | directory request -------> <---------- directory response | make_join request -----------------------> <------------------------------- make_join response | send_join request -----------------------> <------------------------------- send_join response | <---------- join response
The first part of the handshake usually involves using the directory server to request the room ID and join candidates. This is covered in more detail on the directory server documentation, below. In the case of a new user joining a room as a result of a received invite, the joining user's homeserver could optimise this step away by picking the origin server of that invite message as the join candidate. However, the joining server should be aware that the origin server of the invite might since have left the room, so should be prepared to fall back on the regular join flow if this optimisation fails.
Once the joining server has the room ID and the join candidates, it then needs to obtain enough information about the room to fill in the required fields of the m.room.member event. It obtains this by selecting a resident from the candidate list, and requesting the make_join endpoint using a GET request, specifying the room ID and the user ID of the new member who is attempting to join.
The resident server replies to this request with a JSON-encoded object having a single key called event; within this is an object whose fields contain some of the information that the joining server will need. Despite its name, this object is not a full event; notably it does not need to be hashed or signed by the resident homeserver. The required fields are:
| Key | Type | Description |
|---|---|---|
| type | String | The value m.room.member |
| auth_events | List | An event-reference list containing the authorization events that would allow this member to join |
| content | Object | The event content |
| depth | Integer | (this field must be present but is ignored; it may be 0) |
| event_id | String | A new event ID specified by the resident homeserver |
| origin | String | The name of the resident homeserver |
| origin_server_ts | Integer | A timestamp added by the resident homeserver |
| prev_events | List | An event-reference list containing the immediate predecessor events |
| room_id | String | The room ID of the room |
| sender | String | The user ID of the joining member |
| state_key | String | The user ID of the joining member |
The content field itself must be an object, containing:
| Key | Type | Description |
|---|---|---|
| membership | String | The value join |
The joining server now has sufficient information to construct the real join event from these protoevent fields. It copies the values of most of them, adding (or replacing) the following fields:
| Key | Type | Description |
|---|---|---|
| event_id | String | A new event ID specified by the joining homeserver |
| origin | String | The name of the joining homeserver |
| origin_server_ts | Integer | A timestamp added by the joining homeserver |
This will be a true event, so the joining server should apply the event-signing algorithm to it, resulting in the addition of the hashes and signatures fields.
To complete the join handshake, the joining server must now submit this new event to an resident homeserver, by using the send_join endpoint. This is invoked using the room ID and the event ID of the new member event.
The resident homeserver then accepts this event into the room's event graph, and responds to the joining server with the full set of state for the newly- joined room. This is returned as a two-element list, whose first element is the integer 200, and whose second element is an object which contains the following keys:
| Key | Type | Description |
|---|---|---|
| auth_chain | List | A list of events giving the authorization chain for this join event |
| state | List | A complete list of the prevailing state events at the instant just before accepting the new m.room.member event |
Note
This section is a work in progress.
Every HTTP request made by a homeserver is authenticated using public key digital signatures. The request method, target and body are signed by wrapping them in a JSON object and signing it using the JSON signing algorithm. The resulting signatures are added as an Authorization header with an auth scheme of X-Matrix. Note that the target field should include the full path starting with /_matrix/..., including the ? and any query parameters if present, but should not include the leading https:, nor the destination server's hostname.
Step 1 sign JSON:
{ "method": "GET", "uri": "/target", "origin": "origin.hs.example.com", "destintation": "destination.hs.example.com", "content": { JSON content ... }, "signatures": { "origin.hs.example.com": { "ed25519:key1": "ABCDEF..." } } }
Step 2 add Authorization header:
GET /target HTTP/1.1 Authorization: X-Matrix origin=origin.example.com,key="ed25519:key1",sig="ABCDEF..." Content-Type: application/json { JSON content ... }
Example python code:
def authorization_headers(origin_name, origin_signing_key, destination_name, request_method, request_target, content_json=None): request_json = { "method": request_method, "uri": request_target, "origin": origin_name, "destination": destination_name, } if content_json is not None: request["content"] = content_json signed_json = sign_json(request_json, origin_name, origin_signing_key) authorization_headers = [] for key, sig in signed_json["signatures"][origin_name].items(): authorization_headers.append(bytes( "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( origin_name, key, sig, ) )) return ("Authorization", authorization_headers)
Responses are authenticated by the TLS server certificate. A homeserver should not send a request until it has authenticated the connected server to avoid leaking messages to eavesdroppers.
Requests are authenticated at the HTTP layer rather than at the TLS layer because HTTP services like Matrix are often deployed behind load balancers that handle the TLS and these load balancers make it difficult to check TLS client certificates.
A home server may provide a TLS client certificate and the receiving home server may check that the client certificate matches the certificate of the origin home server.
Note
This section is a work in progress.
The server API for presence is based entirely on exchange of the following EDUs. There are no PDUs or Federation Queries involved.
Performing a presence update and poll subscription request:
EDU type: m.presence Content keys: push: (optional): list of push operations. Each should be an object with the following keys: user_id: string containing a User ID presence: "offline"|"unavailable"|"online"|"free_for_chat" status_msg: (optional) string of free-form text last_active_ago: milliseconds since the last activity by the user poll: (optional): list of strings giving User IDs unpoll: (optional): list of strings giving User IDs
The presence of this combined message is two-fold: it informs the recipient server of the current status of one or more users on the sending server (by the push key), and it maintains the list of users on the recipient server that the sending server is interested in receiving updates for, by adding (by the poll key) or removing them (by the unpoll key). The poll and unpoll lists apply changes to the implied list of users; any existing IDs that the server sent as poll operations in a previous message are not removed until explicitly requested by a later unpoll.
On receipt of a message containing a non-empty poll list, the receiving server should immediately send the sending server a presence update EDU of its own, containing in a push list the current state of every user that was in the original EDU's poll list.
Sending a presence invite:
EDU type: m.presence_invite Content keys: observed_user: string giving the User ID of the user whose presence is requested (i.e. the recipient of the invite) observer_user: string giving the User ID of the user who is requesting to observe the presence (i.e. the sender of the invite)
Accepting a presence invite:
EDU type: m.presence_accept Content keys - as for m.presence_invite
Rejecting a presence invite:
EDU type: m.presence_deny Content keys - as for m.presence_invite
The server API for profiles is based entirely on the following Federation Queries. There are no additional EDU or PDU types involved, other than the implicit m.presence and m.room.member events (see section below).
Querying profile information:
Query type: profile Arguments: user_id: the ID of the user whose profile to return field: (optional) string giving a field name Returns: JSON object containing the following keys: displayname: string of free-form text avatar_url: string containing an HTTP-scheme URL
If the query contains the optional field key, it should give the name of a result field. If such is present, then the result should contain only a field of that name, with no others present. If not, the result should contain as much of the user's profile as the home server has available and can make public.
The server API for directory queries is also based on Federation Queries.
Querying directory information:
Query type: directory Arguments: room_alias: the room alias to query Returns: JSON object containing the following keys: room_id: string giving the underlying room ID the alias maps to servers: list of strings giving the join candidates
The list of join candidates is a list of server names that are likely to hold the given room; these are servers that the requesting server may wish to use as resident servers as part of the remote join handshake. This list may or may not include the server answering the query.
Note
This section is a work in progress.
The attacker could attempt to prevent delivery of messages to or from the victim in order to:
An attacker could cause the victims server to exhaust a particular resource (e.g. open TCP connections, CPU, memory, disk storage)
An attacker could send messages which created an unrecoverable "split-brain" state in the cluster such that the victim's servers could no longer derive a consistent view of the chatroom state.
An attacker could convince the victim to accept invalid messages which the victim would then include in their view of the chatroom history. Other servers in the chatroom would reject the invalid messages and potentially reject the victims messages as well since they depended on the invalid messages.
An attacker could try to firewall traffic between the victim's server and some or all of the other servers in the chatroom.
An attacker could send large volumes of messages to a chatroom with the victim making the chatroom unusable.
An attacker could attempt to ban a user from a chatroom with the necessary authorisation.
An attacker could try to send a message claiming to be from the victim without the victim having sent the message in order to:
An attacker could try to alter the contents of an existing message from the victim.
An attacker could try to send a new message purporting to be from the victim with a phony "origin" field.
The attacker could try to send a high volume of solicited or unsolicited messages to the victim in order to:
An attacker could try to send messages to victims who do not wish to receive them.
An attacker could send abusive or threatening messages to the victim
The attacker could try to access message contents or metadata for messages sent by the victim or to the victim that were not intended to reach the attacker in order to:
An attacker could try to expose the message contents or metadata during transmission between the servers.
An attacker could try to convince servers within a chatroom to send messages to a server it controls that was not authorised to be within the chatroom.
An attacker could take control of a server within a chatroom to expose message contents or metadata for messages in that room.
To assist in the development of compatible implementations, the following test values may be useful for verifying the cryptographic event signing code.
The following test vectors all use the 32-byte value given by the following Base64-encoded string as the seed for generating the ed25519 signing key:
SIGNING_KEY_SEED = decode_base64( "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1" )
In each case, the server name and key ID are as follows:
SERVER_NAME = "domain" KEY_ID = "ed25519:1"
Given an empty JSON object:
{}
The JSON signing algorithm should emit the following signed data:
{ "signatures": { "domain": { "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" } } }
Given the following JSON object with data values in it:
{ "one": 1, "two": "Two" }
The JSON signing algorithm should emit the following signed JSON:
{ "one": 1, "signatures": { "domain": { "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw" } }, "two": "Two" }
Given the following minimally-sized event:
{ "event_id": "$0:domain", "origin": "domain", "origin_server_ts": 1000000, "signatures": {}, "type": "X", "unsigned": { "age_ts": 1000000 } }
The event signing algorithm should emit the following signed event:
{ "event_id": "$0:domain", "hashes": { "sha256": "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI" }, "origin": "domain", "origin_server_ts": 1000000, "signatures": { "domain": { "ed25519:1": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA" } }, "type": "X", "unsigned": { "age_ts": 1000000 } }
Given the following event containing redactable content:
{ "content": { "body": "Here is the message content", }, "event_id": "$0:domain", "origin": "domain", "origin_server_ts": 1000000, "type": "m.room.message", "room_id": "!r:domain", "sender": "@u:domain", "signatures": {}, "unsigned": { "age_ts": 1000000 } }
The event signing algorithm should emit the following signed event:
{ "content": { "body": "Here is the message content", }, "event_id": "$0:domain", "hashes": { "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g" }, "origin": "domain", "origin_server_ts": 1000000, "type": "m.room.message", "room_id": "!r:domain", "sender": "@u:domain", "signatures": { "domain": { "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" } }, "unsigned": { "age_ts": 1000000 } }