{ "content": { "body": "thinks this is an example emote", "format": "org.matrix.custom.html", "formatted_body": "thinks this is an example emote", "msgtype": "m.emote" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "body": "something-important.doc", "filename": "something-important.doc", "info": { "mimetype": "application/msword", "size": 46144 }, "msgtype": "m.file", "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "body": "filename.jpg", "info": { "h": 398, "mimetype": "image/jpeg", "size": 31037, "w": 394 }, "msgtype": "m.image", "url": "mxc://example.org/JWEIFJgwEIhweiWJE" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "body": "Big Ben, London, UK", "geo_uri": "geo:51.5008,0.1247", "info": { "thumbnail_info": { "h": 300, "mimetype": "image/jpeg", "size": 46144, "w": 300 }, "thumbnail_url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe" }, "msgtype": "m.location" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "body": "This is an example notice", "format": "org.matrix.custom.html", "formatted_body": "This is an example notice", "msgtype": "m.notice" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "admin_contact"<EMAIL_ADDRESS>"body": "Human-readable message to explain the notice", "limit_type": "monthly_active_user", "msgtype": "m.server_notice", "server_notice_type": "m.server_notice.usage_limit_reached" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "content": { "body": "This is an example text message", "format": "org.matrix.custom.html", "formatted_body": "This is an example text message", "msgtype": "m.text" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
{ "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://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", "w": 480 }, "msgtype": "m.video", "url": "mxc://example.org/a526eYUSFFxlgbQYZmo442" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
Message Event
NB: Usage of this event is discouraged in favour of the receipts module. Most clients will not recognise this event. Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: delivered (sent when the event has been received) and read (sent when the event has been observed by the end-user). The target_event_id should reference the m.room.message event being acknowledged.
| Content Key | Type | Description |
|---|---|---|
| target_event_id | string | Required. The event that this feedback is related to. |
| type | enum | Required. The type of feedback. One of: ["delivered", "read"] |
Example:
{ "content": { "target_event_id": "$WEIGFHFW:localhost", "type": "delivered" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message.feedback", "unsigned": { "age": 1234 } }
A room has an opaque room ID which is not human-friendly to read. A room alias is human-friendly, but not all rooms have room aliases. The room name is a human-friendly string designed to be displayed to the end-user. The room name is not unique, as multiple rooms can have the same room name set.
A room with an m.room.name event with an absent, null, or empty name field should be treated the same as a room with no m.room.name event.
An event of this type is automatically created when creating a room using /createRoom with the name key.
| Content Key | Type | Description |
|---|---|---|
| name | string | Required. The name of the room. This MUST NOT exceed 255 bytes. |
Example:
{ "content": { "name": "The room name" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "state_key": "", "type": "m.room.name", "unsigned": { "age": 1234 } }
A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using /createRoom with the topic key.
| Content Key | Type | Description |
|---|---|---|
| topic | string | Required. The topic text. |
Example:
{ "content": { "topic": "A room topic" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "state_key": "", "type": "m.room.topic", "unsigned": { "age": 1234 } }
A picture that is associated with the room. This can be displayed alongside the room information.
| Content Key | Type | Description |
|---|---|---|
| info | ImageInfo | Metadata about the image referred to in url. |
| url | string | Required. The URL to the image. |
| ImageInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
| thumbnail_url | string | The URL (typically MXC URI) to a thumbnail of the image. Only present if the thumbnail is unencrypted. |
| thumbnail_file | EncryptedFile | Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. |
| thumbnail_info | ThumbnailInfo | Metadata about the image referred to in thumbnail_url. |
| ThumbnailInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
Example:
{ "content": { "info": { "h": 398, "mimetype": "image/jpeg", "size": 31037, "w": 394 }, "url": "mxc://example.org/JWEIFJgwEIhweiWJE" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "state_key": "", "type": "m.room.avatar", "unsigned": { "age": 1234 } }
This event is used to "pin" particular events in a room for other participants to review later. The order of the pinned events is guaranteed and based upon the order supplied in the event. Clients should be aware that the current user may not be able to see some of the events pinned due to visibility settings in the room. Clients are responsible for determining if a particular event in the pinned list is displayable, and have the option to not display it if it cannot be pinned in the client.
| Content Key | Type | Description |
|---|---|---|
| pinned | [string] | Required. An ordered list of event IDs to pin. |
Example:
{ "content": { "pinned": [ "$someevent:example.org" ] }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "state_key": "", "type": "m.room.pinned_events", "unsigned": { "age": 1234 } }
Each m.room.message MUST have a msgtype key which identifies the type of message being sent. Each type has their own required and optional keys, as outlined below. If a client cannot display the given msgtype then it SHOULD display the fallback plain text body key instead.
Some message types support HTML in the event content that clients should prefer to display if available. Currently m.text, m.emote, and m.notice support an additional format parameter of org.matrix.custom.html. When this field is present, a formatted_body with the HTML must be provided. The plain text version of the HTML should be provided in the body.
Clients should limit the HTML they render to avoid Cross-Site Scripting, HTML injection, and similar attacks. The strongly suggested set of HTML tags to permit, denying the use and rendering of anything else, is: font, del, h1, h2, h3, h4, h5, h6, blockquote, p, a, ul, ol, sup, sub, li, b, i, u, strong, em, strike, code, hr, br, div, table, thead, tbody, tr, th, td, caption, pre, span, img.
Not all attributes on those tags should be permitted as they may be avenues for other disruption attempts, such as adding onclick handlers or excessively large text. Clients should only permit the attributes listed for the tags below. Where data-mx-bg-color and data-mx-color are listed, clients should translate the value (a 6-character hex color code) to the appropriate CSS/attributes for the tag.
| font: | data-mx-bg-color, data-mx-color |
|---|---|
| span: | data-mx-bg-color, data-mx-color |
| a: | name, target, href (provided the value is not relative and has a scheme matching one of: https, http, ftp, mailto, magnet) |
| img: | width, height, alt, title, src (provided it is a Matrix Content (MXC) URI) |
| ol: | start |
| code: | class (only classes which start with language- for syntax highlighting) |
Additionally, web clients should ensure that all a tags get a rel="noopener" to prevent the target page from referencing the client's tab/window.
Tags must not be nested more than 100 levels deep. Clients should only support the subset of tags they can render, falling back to other representations of the tags where possible. For example, a client may not be able to render tables correctly and instead could fall back to rendering tab-delimited text.
In addition to not rendering unsafe HTML, clients should not emit unsafe HTML in events. Likewise, clients should not generate HTML that is not needed, such as extra paragraph tags surrounding text due to Rich Text Editors. HTML included in events should otherwise be valid, such as having appropriate closing tags, appropriate attributes (considering the custom ones defined in this specification), and generally valid structure.
A special tag, mx-reply, may appear on rich replies (described below) and should be allowed if, and only if, the tag appears as the very first tag in the formatted_body. The tag cannot be nested and cannot be located after another tag in the tree. Because the tag contains HTML, an mx-reply is expected to have a partner closing tag and should be treated similar to a div. Clients that support rich replies will end up stripping the tag and its contents and therefore may wish to exclude the tag entirely.
Note
A future iteration of the specification will support more powerful and extensible message formatting options, such as the proposal MSC1225.
This message is the most basic message and is used to represent text.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. The body of the message. |
| msgtype | enum | Required. Must be 'm.text'. |
| format | string | The format used in the formatted_body. Currently only org.matrix.custom.html is supported. |
| formatted_body | string | The formatted version of the body. This is required if format is specified. |
Example:
{ "content": { "body": "This is an example text message", "format": "org.matrix.custom.html", "formatted_body": "This is an example text message", "msgtype": "m.text" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message is similar to m.text except that the sender is 'performing' the action contained in the body key, similar to /me in IRC. This message should be prefixed by the name of the sender. This message could also be represented in a different colour to distinguish it from regular m.text messages.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. The emote action to perform. |
| msgtype | enum | Required. Must be 'm.emote'. |
| format | string | The format used in the formatted_body. Currently only org.matrix.custom.html is supported. |
| formatted_body | string | The formatted version of the body. This is required if format is specified. |
Example:
{ "content": { "body": "thinks this is an example emote", "format": "org.matrix.custom.html", "formatted_body": "thinks this is an example emote", "msgtype": "m.emote" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
The m.notice type is primarily intended for responses from automated clients. An m.notice message must be treated the same way as a regular m.text message with two exceptions. Firstly, clients should present m.notice messages to users in a distinct manner, and secondly, m.notice messages must never be automatically responded to. This helps to prevent infinite-loop situations where two automated clients continuously exchange messages.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. The notice text to send. |
| msgtype | enum | Required. Must be 'm.notice'. |
| format | string | The format used in the formatted_body. Currently only org.matrix.custom.html is supported. |
| formatted_body | string | The formatted version of the body. This is required if format is specified. |
Example:
{ "content": { "body": "This is an example notice", "format": "org.matrix.custom.html", "formatted_body": "This is an example notice", "msgtype": "m.notice" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message represents a single image and an optional thumbnail.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. A textual representation of the image. This could be the alt text of the image, the filename of the image, or some kind of content description for accessibility e.g. 'image attachment'. |
| info | ImageInfo | Metadata about the image referred to in url. |
| msgtype | enum | Required. Must be 'm.image'. |
| url | string | Required if the file is unencrypted. The URL (typically MXC URI) to the image. |
| file | EncryptedFile | Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. |
| ImageInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
| thumbnail_url | string | The URL (typically MXC URI) to a thumbnail of the image. Only present if the thumbnail is unencrypted. |
| thumbnail_file | EncryptedFile | Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. |
| thumbnail_info | ThumbnailInfo | Metadata about the image referred to in thumbnail_url. |
| ThumbnailInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
Example:
{ "content": { "body": "filename.jpg", "info": { "h": 398, "mimetype": "image/jpeg", "size": 31037, "w": 394 }, "msgtype": "m.image", "url": "mxc://example.org/JWEIFJgwEIhweiWJE" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message represents a generic file.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. A human-readable description of the file. This is recommended to be the filename of the original upload. |
| filename | string | The original filename of the uploaded file. |
| info | FileInfo | Information about the file referred to in url. |
| msgtype | enum | Required. Must be 'm.file'. |
| url | string | Required if the file is unencrypted. The URL (typically MXC URI) to the file. |
| file | EncryptedFile | Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. |
| FileInfo Key | Type | Description |
|---|---|---|
| mimetype | string | The mimetype of the file e.g. application/msword. |
| size | integer | The size of the file in bytes. |
| thumbnail_url | string | The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. |
| thumbnail_file | EncryptedFile | Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. |
| thumbnail_info | ThumbnailInfo | Metadata about the image referred to in thumbnail_url. |
| ThumbnailInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
Example:
{ "content": { "body": "something-important.doc", "filename": "something-important.doc", "info": { "mimetype": "application/msword", "size": 46144 }, "msgtype": "m.file", "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message represents a single audio clip.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. 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 | enum | Required. Must be 'm.audio'. |
| url | string | Required if the file is unencrypted. The URL (typically MXC URI) to the audio clip. |
| file | EncryptedFile | Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. |
| 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:
{ "content": { "body": "Bee Gees - Stayin' Alive", "info": { "duration": 2140786, "mimetype": "audio/mpeg", "size": 1563685 }, "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message represents a real-world location.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'. |
| geo_uri | string | Required. A geo URI representing this location. |
| msgtype | enum | Required. Must be 'm.location'. |
| info | LocationInfo |
| LocationInfo Key | Type | Description |
|---|---|---|
| thumbnail_url | string | The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. |
| thumbnail_file | EncryptedFile | Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. |
| thumbnail_info | ThumbnailInfo | Metadata about the image referred to in thumbnail_url. |
| ThumbnailInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
Example:
{ "content": { "body": "Big Ben, London, UK", "geo_uri": "geo:51.5008,0.1247", "info": { "thumbnail_info": { "h": 300, "mimetype": "image/jpeg", "size": 46144, "w": 300 }, "thumbnail_url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe" }, "msgtype": "m.location" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
This message represents a single video clip.
| Content Key | Type | Description |
|---|---|---|
| body | string | Required. 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 | enum | Required. Must be 'm.video'. |
| url | string | Required if the file is unencrypted. The URL (typically MXC URI) to the video clip. |
| file | EncryptedFile | Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. |
| VideoInfo Key | Type | Description |
|---|---|---|
| duration | integer | The duration of the video in milliseconds. |
| h | integer | The height of the video in pixels. |
| w | integer | The width 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_url | string | The URL (typically MXC URI) to an image thumbnail of the video clip. Only present if the thumbnail is unencrypted. |
| thumbnail_file | EncryptedFile | Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. |
| thumbnail_info | ThumbnailInfo | Metadata about the image referred to in thumbnail_url. |
| ThumbnailInfo Key | Type | Description |
|---|---|---|
| h | integer | The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| w | integer | The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file. |
| mimetype | string | The mimetype of the image, e.g. image/jpeg. |
| size | integer | Size of the image in bytes. |
Example:
{ "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://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", "w": 480 }, "msgtype": "m.video", "url": "mxc://example.org/a526eYUSFFxlgbQYZmo442" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
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 MAY include a client generated thumbnail image for an attachment under a info.thumbnail_url key. The thumbnail SHOULD also be a mxc:// URI. Clients displaying events with attachments can either use the client generated thumbnail or ask its homeserver to generate a thumbnail from the original attachment using the content repository module.
In the event of send failure, 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 can be reduced through clients making use of the transaction ID they used to send a particular event. The transaction ID used will be included in the event's unsigned data as transaction_id when it arrives through the event stream.
Clients unable to make use of the transaction ID are likely to experience flickering when the remote echo arrives on the event stream before the request to send the message completes. In that case the event arrives before the client has obtained an event ID, making it impossible to identify it as a remote echo. This results in the client displaying the message twice for some time (depending on the server responsiveness) before 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.
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:
Clients SHOULD internationalise the room name to the user's language when using the m.heroes to calculate the name. Clients SHOULD use minimum 5 heroes to calculate room names where possible, but may use more or less to fit better with their user experience.
In some cases, events may wish to reference other events. This could be to form a thread of messages for the user to follow along with, or to provide more context as to what a particular event is describing. Currently, the only kind of relation defined is a "rich reply" where a user may reference another message to create a thread-like conversation.
Relationships are defined under an m.relates_to key in the event's content. If the event is of the type m.room.encrypted, the m.relates_to key MUST NOT be covered by the encryption and instead be put alongside the encryption information held in the content.
Users may wish to reference another message when forming their own message, and clients may wish to better embed the referenced message for the user to have a better context for the conversation being had. This sort of embedding another message in a message is known as a "rich reply", or occasionally just a "reply".
A rich reply is formed through use of an m.relates_to relation for m.in_reply_to where a single key, event_id, is used to reference the event being replied to. The referenced event ID SHOULD belong to the same room where the reply is being sent. Clients should be cautious of the event ID belonging to another room, or being invalid entirely. Rich replies can only be constructed in the form of m.room.message events with a msgtype of m.text or m.notice. Due to the fallback requirements, rich replies cannot be constructed for types of m.emote, m.file, etc. Rich replies may reference any other m.room.message event, however. Rich replies may reference another event which also has a rich reply, infinitely.
An m.in_reply_to relationship looks like the following:
{ ... "type": "m.room.message", "content": { "msgtype": "m.text", "body": "", "format": "org.matrix.custom.html", "formatted_body": "", "m.relates_to": { "m.in_reply_to": { "event_id": "$another:event.com" } } } }
Some clients may not have support for rich replies and therefore need a fallback to use instead. Clients that do not support rich replies should render the event as if rich replies were not special.
Clients that do support rich replies MUST provide the fallback format on replies, and MUST strip the fallback before rendering the reply. Rich replies MUST have a format of org.matrix.custom.html and therefore a formatted_body alongside the body and appropriate msgtype. The specific fallback text is different for each msgtype, however the general format for the body is:
> <@alice:example.org> This is the original body This is where the reply goes
The formatted_body should use the following template:
<mx-reply>
 In reply to @alice:example.org

 </mx-reply> This is where the reply goes.
If the related event does not have a formatted_body, the event's body should be considered after encoding any HTML special characters. Note that the href in both of the anchors use a matrix.to URI.
Clients which support rich replies MUST strip the fallback from the event before rendering the event. This is because the text provided in the fallback cannot be trusted to be an accurate representation of the event. After removing the fallback, clients are recommended to represent the event referenced by m.in_reply_to similar to the fallback's representation, although clients do have creative freedom for their user interface. Clients should prefer the formatted_body over the body, just like with other m.room.message events.
To strip the fallback on the body, the client should iterate over each line of the string, removing any lines that start with the fallback prefix ("> ", including the space, without quotes) and stopping when a line is encountered without the prefix. This prefix is known as the "fallback prefix sequence".
To strip the fallback on the formatted_body, the client should remove the entirety of the mx-reply tag.
Using the prefix sequence, the first line of the related event's body should be prefixed with the user's ID, followed by each line being prefixed with the fallback prefix sequence. For example:
> <@alice:example.org> This is the first line > This is the second line This is the reply
The formatted_body uses the template defined earlier in this section.
Similar to the fallback for m.text, each line gets prefixed with the fallback prefix sequence. However an asterisk should be inserted before the user's ID, like so:
> * <@alice:example.org> feels like today is going to be a great day This is the reply
The formatted_body has a subtle difference for the template where the asterisk is also inserted ahead of the user's ID:
<mx-reply>
 In reply to * @alice:example.org

 </mx-reply> This is where the reply goes.
The related event's body would be a file name, which may not be very descriptive. The related event should additionally not have a format or formatted_body in the content - if the event does have a format and/or formatted_body, those fields should be ignored. Because the filename alone may not be descriptive, the related event's body should be considered to be "sent a file." such that the output looks similar to the following:
> <@alice:example.org> sent a file. This is the reply
<mx-reply>
 In reply to @alice:example.org
 sent a file.
 </mx-reply> This is where the reply goes.
For m.image, the text should be "sent an image.". For m.video, the text should be "sent a video.". For m.audio, the text should be "sent an audio file".
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, although end to end encryption is in development (see 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. In this version of the spec, only two-party communication is supported (e.g. between two peers, or between a peer and a multi-point conferencing unit). This means that clients MUST only send call events to rooms with exactly two participants.
Message Event
This event is sent by the caller when they wish to establish a call.
| Content Key | Type | Description |
|---|---|---|
| call_id | string | Required. A unique identifier for the call. |
| offer | Offer | Required. The session description object |
| version | integer | Required. The version of the VoIP specification this message adheres to. This specification is version 0. |
| lifetime | integer | Required. 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 Key | Type | Description |
|---|---|---|
| type | enum | Required. The type of session description. Must be 'offer'. |
| sdp | string | Required. The SDP text of the session description. |
Example:
{ "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": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.call.invite", "unsigned": { "age": 1234 } }
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 | Required. The ID of the call this event relates to. |
| candidates | [Candidate] | Required. Array of objects describing the candidates. |
| version | integer | Required. The version of the VoIP specification this messages adheres to. This specification is version 0. |
| Candidate Key | Type | Description |
|---|---|---|
| sdpMid | string | Required. The SDP media type this candidate is intended for. |
| sdpMLineIndex | number | Required. The index of the SDP 'm' line this candidate is intended for. |
| candidate | string | Required. The SDP 'a' line of the candidate. |
Example:
{ "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": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.call.candidates", "unsigned": { "age": 1234 } }
Message Event
This event is sent by the callee when they wish to answer the call.
| Content Key | Type | Description |
|---|---|---|
| call_id | string | Required. The ID of the call this event relates to. |
| answer | Answer | Required. The session description object |
| version | number | Required. |
| Answer Key | Type | Description |
|---|---|---|
| type | enum | Required. The type of session description. Must be 'answer'. |
| sdp | string | Required. The SDP text of the session description. |
Example:
{ "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": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.call.answer", "unsigned": { "age": 1234 } }
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 | Required. The ID of the call this event relates to. |
| version | integer | Required. The version of the VoIP specification this message adheres to. This specification is version 0. |
| reason | enum | Optional error reason for the hangup. This should not be provided when the user naturally ends or rejects the call. When there was an error in the call negotiation, this should be ice_failed for when ICE negotiation fails or invite_timeout for when the other party did not answer in time. One of: ["ice_failed", "invite_timeout"] |
Example:
{ "content": { "call_id": "12345", "version": 0 }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.call.hangup", "unsigned": { "age": 1234 } }
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 |
|---|---|---|
| username | string | Required. The username to use. |
| password | string | Required. The password to use. |
| uris | [string] | Required. A list of TURN URIs |
| ttl | integer | Required. The time-to-live in seconds |
Example request:
GET /_matrix/client/r0/voip/turnServer HTTP/1.1
Responses:
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 }
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
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.
Informs the client of the list of users currently typing.
| Content Key | Type | Description |
|---|---|---|
| user_ids | [string] | Required. The list of user IDs typing in this room, if any. |
Example:
{ "content": { "user_ids": [ "@alice:matrix.org", "@bob:example.com" ] }, "room_id": "!jEsUZKDJdhlrceRyVU:example.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 | ||
| typing | boolean | Required. Whether the user is typing or not. If false, the timeout key can be omitted. |
| timeout | integer | The length of time in milliseconds to mark this user as typing. |
Example request:
PUT /_matrix/client/r0/rooms/%21wefh3sfukhs%3Aexample.com/typing/%40alice%3Aexample.com HTTP/1.1 Content-Type: application/json { "typing": true, "timeout": 30000 }
Responses:
Status code 200:
The new typing state was set.
Example
{}
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
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. See the Receiving notifications section for more information on how read receipts affect notification counts.
Each user_id, receipt_type pair must be associated with only a single event_id.
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 Key | Type | Description |
|---|---|---|
| m.read | Users | A collection of users who have sent m.read receipts for this event. |
| 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 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": "!jEsUZKDJdhlrceRyVU:example.org", "type": "m.receipt" }
In /sync, receipts are listed under the ephemeral array of events for a given 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". Clients SHOULD NOT send read receipts for events sent by their own user.
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/r0/rooms/%21wefuh21ffskfuh345%3Aexample.com/receipt/m.read/%241924376522eioj%3Aexample.com HTTP/1.1 Content-Type: application/json {}
Responses:
Status code 200:
The receipt was sent.
Example
{}
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
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.
The history for a given room may be split into three sections: messages the user has read (or indicated they aren't interested in them), messages the user might have read some but not others, and messages the user hasn't seen yet. The "fully read marker" (also known as a "read marker") marks the last event of the first section, whereas the user's read receipt marks the last event of the second section.
The user's fully read marker is kept as an event in the room's account data. The event may be read to determine the user's current fully read marker location in the room, and just like other account data events the event will be pushed down the event stream when updated.
The fully read marker is kept under an m.fully_read event. If the event does not exist on the user's account data, the fully read marker should be considered to be the user's read receipt location.
The current location of the user's read marker in a room. This event appears in the user's room account data for the room the marker is applicable for.
| Content Key | Type | Description |
|---|---|---|
| event_id | string | Required. The event the user's read marker is located at in the room. |
Example:
{ "content": { "event_id": "$someplace:example.org" }, "room_id": "!somewhere:example.org", "type": "m.fully_read" }
The client cannot update fully read markers by directly modifying the m.fully_read account data event. Instead, the client must make use of the read markers API to change the values.
The read markers API can additionally update the user's read receipt (m.read) location in the same operation as setting the fully read marker location. This is because read receipts and read markers are commonly updated at the same time, and therefore the client might wish to save an extra HTTP call. Providing an m.read location performs the same task as a request to /receipts/m.read/$event:example.org.
Sets the position of the read marker for a given room, and optionally the read receipt's location.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| roomId | string | Required. The room ID to set the read marker in for the user. |
| JSON body parameters | ||
| m.fully_read | string | Required. The event ID the read marker should be located at. The event MUST belong to the room. |
| m.read | string | The event ID to set the read receipt location at. This is equivalent to calling /receipt/m.read/$elsewhere:example.org and is provided here to save that extra call. |
Example request:
POST /_matrix/client/r0/rooms/%21somewhere%3Aexample.org/read_markers HTTP/1.1 Content-Type: application/json { "m.fully_read": "$somewhere:example.org", "m.read": "$elsewhere:example.org" }
Responses:
Status code 200:
The read marker, and read receipt if provided, have been updated.
Example
{}
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
The server MUST prevent clients from setting m.fully_read directly in room account data. The server must additionally ensure that it treats the presence of m.read in the /read_markers request the same as how it would for a request to /receipts/m.read/$event:example.org.
Upon updating the m.fully_read event due to a request to /read_markers, the server MUST send the updated account data event through to the client via the event stream (eg: /sync), provided any applicable filters are also satisfied.
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. Presence events are sent to interested parties where users share a room membership.
User's presence state is represented by the presence key, which is an enum of one of the following:
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 | Required. The presence state for this user. One of: ["online", "offline", "unavailable"] |
| currently_active | boolean | Whether the user is currently active |
| status_msg | string | An optional description to accompany the presence. |
Example:
{ "content": { "avatar_url": "mxc://localhost:wefuiwegh8742w", "currently_active": false, "last_active_ago": 2478593, "presence": "online", "status_msg": "Making cupcakes" }, "sender": "@example:localhost", "type": "m.presence" }
Clients can manually set/get their presence 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 | ||
| presence | enum | Required. The new presence state. One of: ["online", "offline", "unavailable"] |
| status_msg | string | The status message to attach to this state. |
Example request:
PUT /_matrix/client/r0/presence/%40alice%3Aexample.com/status HTTP/1.1 Content-Type: application/json { "presence": "online", "status_msg": "I am here." }
Responses:
Status code 200:
The new presence state was set.
Example
{}
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
Get the given user's presence state.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| userId | string | Required. The user whose presence state to get. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| presence | enum | Required. This user's presence. One of: ["online", "offline", "unavailable"] |
| last_active_ago | integer | The length of time in milliseconds since an action was performed by this user. |
| status_msg | string or null | The state message for this user if one was set. |
| currently_active | boolean | Whether the user is currently active |
Example request:
GET /_matrix/client/r0/presence/%40alice%3Aexample.com/status HTTP/1.1
Responses:
Status code 200:
The presence state for this user.
Example
{ "presence": "unavailable", "last_active_ago": 420845 }
Status code 403:
You are not allowed to see this user's presence status.
Example
{ "errcode": "M_FORBIDDEN", "error": "You are not allowed to see their presence" }
Status code 404:
There is no presence state for this user. This user may not exist or isn't exposing presence information to you.
Example
{ "errcode": "M_UNKNOWN", "error": "An unknown error occurred" }
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 online. This timestamp is presented via a key called last_active_ago which gives the relative number of milliseconds since the pro-active event.
To reduce the number of presence updates sent to clients the server may include a currently_active boolean field when the presence state is online. When true, the server will not send further updates to the last active time until an update is sent to the client with either a) currently_active set to false or b) a presence state other than online. During this period clients must consider the user to be currently active, irrespective of the last active time.
The last active time must be up to date whenever the server gives a presence event to the client. The currently_active mechanism should purely be used by servers to stop sending continuous presence updates, as opposed to disabling last active tracking entirely. Thus clients can fetch up to date last active times by explicitly requesting the presence for a given user.
The server will automatically set a user's presence to unavailable if their last active time was over a threshold value (e.g. 5 minutes). Clients can manually set a user's presence to unavailable. Any activity that bumps the last active time on any of the user's clients will cause the server to automatically set their presence to online.
Presence information is shared with all users who share a room with the target user. In large public rooms this could be undesirable.
The content repository (or "media repository") allows users to upload files to their homeserver for later user. For example, files which the user wants to send to a room would be uploaded here, as would an avatar the user wants to use.
Uploads are POSTed to a resource on the user's local homeserver which returns a MXC URI which can later be 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).
When serving content, the server SHOULD provide a Content-Security-Policy header. The recommended policy is sandbox; default-src 'none'; script-src 'none'; plugin-types application/pdf; style-src 'unsafe-inline'; object-src 'self';.
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.
Clients can upload and download content using the following HTTP APIs.
Upload some content to the content repository.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| header parameters | ||
| Content-Type | string | The content type of the file being uploaded |
| query parameters | ||
| filename | string | The name of the file being uploaded |
Response format:
| Parameter | Type | Description |
|---|---|---|
| content_uri | string | Required. The MXC URI to the uploaded content. |
Example request:
POST /_matrix/media/r0/upload?filename=War+and+Peace.pdf HTTP/1.1 Content-Type: Content-Type: application/pdf <bytes>
Responses:
Status code 200:
The MXC URI for the uploaded content.
Example
{ "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" }
Status code 403:
The user does not have permission to upload the content. Some reasons for this error include:
Example
{ "errcode": "M_FORBIDDEN", "error": "Cannot upload this content" }
Status code 413:
The uploaded content is too large for the server.
Example
{ "errcode": "M_TOO_LARGE", "error": "Cannot upload files larger than 100mb" }
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
Download content from the content repository.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | No. |
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 | ||
| allow_remote | boolean | Indicates to the server that it should not attempt to fetch the media if it is deemed remote. This is to prevent routing loops where the server contacts itself. Defaults to true if not provided. |
Response headers:
| Parameter | Type | Description |
|---|---|---|
| Content-Type | string | The content type of the file that was previously uploaded. |
| Content-Disposition | string | The name of the file that was previously uploaded, if set. |
Response format:
| Parameter | Type | Description |
|---|---|---|
|  | file | Required. The bytes for the uploaded file. |
Example request:
GET /_matrix/media/r0/download/matrix.org/ascERGshawAWawugaAcauga?allow_remote=False HTTP/1.1
Responses:
Status code 200:
The content that was previously uploaded.
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
Status code 502:
The content is too large for the server to serve.
Example
{ "errcode": "M_TOO_LARGE", "error": "Content is too large to serve" }
Download content from the content repository. This is the same as the download endpoint above, except permitting a desired file name.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | No. |
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) |
| fileName | string | Required. A filename to give in the Content- Disposition header. |
| query parameters | ||
| allow_remote | boolean | Indicates to the server that it should not attempt to fetch the media if it is deemed remote. This is to prevent routing loops where the server contacts itself. Defaults to true if not provided. |
Response headers:
| Parameter | Type | Description |
|---|---|---|
| Content-Type | string | The content type of the file that was previously uploaded. |
| Content-Disposition | string | The fileName requested or the name of the file that was previously uploaded, if set. |
Response format:
| Parameter | Type | Description |
|---|---|---|
|  | file | Required. The bytes for the uploaded file. |
Example request:
GET /_matrix/media/r0/download/matrix.org/ascERGshawAWawugaAcauga/filename.jpg?allow_remote=False HTTP/1.1
Responses:
Status code 200:
The content that was previously uploaded.
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
Status code 502:
The content is too large for the server to serve.
Example
{ "errcode": "M_TOO_LARGE", "error": "Content is too large to serve" }
Download a thumbnail of content from the content repository. See the thumbnailing section for more information.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | No. |
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 | Required. The desired width of the thumbnail. The actual thumbnail may be larger than the size specified. |
| height | integer | Required. The desired height of the thumbnail. The actual thumbnail may be larger than the size specified. |
| method | enum | The desired resizing method. See the thumbnailing section for more information. One of: ["crop", "scale"] |
| allow_remote | boolean | Indicates to the server that it should not attempt to fetch the media if it is deemed remote. This is to prevent routing loops where the server contacts itself. Defaults to true if not provided. |
Response headers:
| Parameter | Type | Description |
|---|---|---|
| Content-Type | string | The content type of the thumbnail. |
Response format:
| Parameter | Type | Description |
|---|---|---|
|  | file | Required. The bytes for the thumbnail. |
Example request:
GET /_matrix/media/r0/thumbnail/example.org/ascERGshawAWawugaAcauga?width=64&height=64&method=scale&allow_remote=False HTTP/1.1
Responses:
Status code 200:
A thumbnail of the requested content.
Status code 400:
The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
Example
{ "errcode": "M_UNKNOWN", "error": "Cannot generate thumbnails for the requested content" }
Status code 413:
The local content is too large for the server to thumbnail.
Example
{ "errcode": "M_TOO_LARGE", "error": "Content is too large to thumbnail" }
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
Status code 502:
The remote content is too large for the server to thumbnail.
Example
{ "errcode": "M_TOO_LARGE", "error": "Content is too large to thumbnail" }
Get information about a URL for the client. Typically this is called when a client sees a URL in a message and wants to render a preview for the user.
Note
Clients should consider avoiding this endpoint for URLs posted in encrypted rooms. Encrypted rooms often contain more sensitive information the users do not want to share with the homeserver, and this can mean that the URLs being shared should also not be shared with the homeserver.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| query parameters | ||
| url | string | Required. The URL to get a preview of. |
| ts | integer | The preferred point in time to return a preview for. The server may return a newer version if it does not have the requested version available. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| matrix:image:size | integer | The byte-size of the image. Omitted if there is no image attached. |
| og:image | string | An MXC URI to the image. Omitted if there is no image. |
Example request:
GET /_matrix/media/r0/preview_url?url=https%3A%2F%2Fmatrix.org&ts=1510610716656 HTTP/1.1
Responses:
Status code 200:
The OpenGraph data for the URL, which may be empty. Some values are replaced with matrix equivalents if they are provided in the response. The differences from the OpenGraph protocol are described here.
Example
{ "og:title": "Matrix Blog Post", "og:description": "This is a really cool blog post from matrix.org", "og:image": "mxc://example.com/ascERGshawAWawugaAcauga", "og:image:type": "image/png", "og:image:height": 48, "og:image:width": 48, "matrix:image:size": 102400 }
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests", "retry_after_ms": 2000 }
This endpoint allows clients to retrieve the configuration of the content repository, such as upload limitations. Clients SHOULD use this as a guide when using content repository endpoints. All values are intentionally left optional. Clients SHOULD follow the advice given in the field description when the field is not available.
NOTE: Both clients and server administrators should be aware that proxies between the client and the server may affect the apparent behaviour of content repository APIs, for example, proxies may enforce a lower upload size limit than is advertised by the server on this endpoint.
| Rate-limited: | Yes. |
|---|---|
| Requires auth: | Yes. |
Request format:
No parameters
Response format:
| Parameter | Type | Description |
|---|---|---|
| m.upload.size | integer | The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content. If not listed or null, the size limit should be treated as unknown. |
Example request:
GET /_matrix/media/r0/config HTTP/1.1
Responses:
Status code 200:
The public content repository configuration for the matrix server.
Example
{ "m.upload.size": 50000000 }
Status code 429:
This request was rate-limited.
Example
{ "errcode": "M_UNKNOWN", "error": "An unknown error occurred" }
The homeserver SHOULD be able to supply thumbnails for uploaded images and videos. The exact file types which can be thumbnailed are not currently specified - see Issue #1938 for more information.
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.
The dimensions given to the thumbnail API are the minimum size the client would prefer. Servers must never return thumbnails smaller than the client's requested dimensions, unless the content being thumbnailed is smaller than the dimensions. When the content is smaller than the requested dimensions, servers should return the original content rather than thumbnail it.
Servers SHOULD produce thumbnails with the following dimensions and methods:
Servers MUST NOT upscale thumbnails under any circumstance. Servers MUST NOT return a smaller thumbnail than requested, unless the original content makes that impossible.
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:
This module provides a means by which clients can exchange signalling messages without them being stored permanently as part of a shared communication history. A message is delivered exactly once to each client device.
The primary motivation for this API is exchanging data that is meaningless or undesirable to persist in the room DAG - for example, one-time authentication tokens or key data. It is not intended for conversational data, which should be sent using the normal /rooms/<room_id>/send API for consistency throughout Matrix.
To send a message to other devices, a client should call /sendToDevice. Only one message can be sent to each device per transaction, and they must all have the same event type. The device ID in the request body can be set to * to request that the message be sent to all known devices.
If there are send-to-device messages waiting for a client, they will be returned by /sync, as detailed in Extensions to /sync. Clients should inspect the type of each returned event, and ignore any they do not understand.
Servers should store pending messages for local users until they are successfully delivered to the destination device. When a client calls /sync with an access token which corresponds to a device with pending messages, the server should list the pending messages, in order of arrival, in the response body.
When the client calls /sync again with the next_batch token from the first response, the server should infer that any send-to-device messages in that response have been delivered successfully, and delete them from the store.
If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response. 100 messages is recommended as a reasonable limit.
If the client sends messages to users on remote domains, those messages should be sent on to the remote servers via federation.
This endpoint is used to send send-to-device events to a set of client devices.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| eventType | string | Required. The type of event to send. |
| txnId | string | Required. The transaction ID for this event. Clients should generate an ID unique across requests with the same access token; it will be used by the server to ensure idempotency of requests. |
| JSON body parameters | ||
| messages | {string: {string: EventContent}} | The messages to send. A map from user ID, to a map from device ID to message body. The device ID may also be *, meaning all known devices for the user. |
Example request:
PUT /_matrix/client/r0/sendToDevice/m.new_device/35 HTTP/1.1 Content-Type: application/json { "messages": { "@alice:example.com": { "TLLBEANAAG": { "example_content_key": "value" } } } }
Response:
Status code 200:
The message was successfully sent.
Example
{}
This module adds the following properties to the /sync response:
| Parameter | Type | Description |
|---|---|---|
| to_device | ToDevice | Optional. Information on the send-to-device messages for the client device. |
ToDevice
| Parameter | Type | Description |
|---|---|---|
| events | [Event] | List of send-to-device messages. |
Event
| Parameter | Type | Description |
|---|---|---|
| content | EventContent | The content of this event. The fields in this object will vary depending on the type of event. |
| sender | string | The Matrix user ID of the user who sent this event. |
| type | string | The type of event. |
Example response:
{ "next_batch": "s72595_4483_1934", "rooms": {"leave": {}, "join": {}, "invite": {}}, "to_device": { "events": [ { "sender": "@alice:example.com", "type": "m.new_device", "content": { "device_id": "XYZABCDE", "rooms": ["!726s6s6q:example.com"] } } ] } }
This module provides a means for a user to manage their devices.
Clients that implement this module should offer the user a list of registered devices, as well as the means to update their display names. Clients should also allow users to delete disused devices.
Gets information about all devices for the current user.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
No parameters
Response format:
| Parameter | Type | Description |
|---|---|---|
| devices | [Device] | A list of all registered devices for this user. |
| Parameter | Type | Description |
|---|---|---|
| device_id | string | Required. Identifier of this device. |
| display_name | string | Display name set by the user for this device. Absent if no name has been set. |
| last_seen_ip | string | The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons). |
| last_seen_ts | integer | The timestamp (in milliseconds since the unix epoch) when this devices was last seen. (May be a few minutes out of date, for efficiency reasons). |
Example request:
GET /_matrix/client/r0/devices HTTP/1.1
Response:
Status code 200:
Device information
Example
{ "devices": [ { "device_id": "QBUAZIFURK", "display_name": "android", "last_seen_ip": "<IP_ADDRESS>", "last_seen_ts":<PHONE_NUMBER>024 } ] }
Gets information on a single device, by device id.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| deviceId | string | Required. The device to retrieve. |
Response format:
| Parameter | Type | Description |
|---|---|---|
| device_id | string | Required. Identifier of this device. |
| display_name | string | Display name set by the user for this device. Absent if no name has been set. |
| last_seen_ip | string | The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons). |
| last_seen_ts | integer | The timestamp (in milliseconds since the unix epoch) when this devices was last seen. (May be a few minutes out of date, for efficiency reasons). |
Example request:
GET /_matrix/client/r0/devices/QBUAZIFURK HTTP/1.1
Responses:
Status code 200:
Device information
Example
{ "device_id": "QBUAZIFURK", "display_name": "android", "last_seen_ip": "<IP_ADDRESS>", "last_seen_ts":<PHONE_NUMBER>024 }
Status code 404:
The current user has no device with the given ID.
Updates the metadata on the given device.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| deviceId | string | Required. The device to update. |
| JSON body parameters | ||
| display_name | string | The new display name for this device. If not given, the display name is unchanged. |
Example request:
PUT /_matrix/client/r0/devices/QBUAZIFURK HTTP/1.1 Content-Type: application/json { "display_name": "My other phone" }
Responses:
Status code 200:
The device was successfully updated.
Example
{}
Status code 404:
The current user has no device with the given ID.
This API endpoint uses the User-Interactive Authentication API.
Deletes the given device, and invalidates any access token associated with it.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| path parameters | ||
| deviceId | string | Required. The device to delete. |
| JSON body parameters | ||
| auth | Authentication Data | Additional authentication information for the user-interactive authentication API. |
| Parameter | Type | Description |
|---|---|---|
| type | string | Required. The login type that the client is attempting to complete. |
| session | string | The value of the session key given by the homeserver. |
Example request:
DELETE /_matrix/client/r0/devices/QBUAZIFURK HTTP/1.1 Content-Type: application/json { "auth": { "type": "example.type.foo", "session": "xxxxx", "example_credential": "verypoorsharedsecret" } }
Responses:
Status code 200:
The device was successfully removed, or had been removed previously.
Example
{}
Status code 401:
The homeserver requires additional authentication information.
Example
{ "flows": [ { "stages": [ "example.type.foo" ] } ], "params": { "example.type.baz": { "example_key": "foobar" } }, "session": "xxxxxxyz", "completed": [ "example.type.foo" ] }
This API endpoint uses the User-Interactive Authentication API.
Deletes the given devices, and invalidates any access token associated with them.
| Rate-limited: | No. |
|---|---|
| Requires auth: | Yes. |
Request format:
| Parameter | Type | Description |
|---|---|---|
| JSON body parameters | ||
| devices | [string] | Required. The list of device IDs to delete. |
| auth | Authentication Data | Additional authentication information for the user-interactive authentication API. |
| Parameter | Type | Description |
|---|---|---|
| type | string | Required. The login type that the client is attempting to complete. |
| session | string | The value of the session key given by the homeserver. |
Example request:
POST /_matrix/client/r0/delete_devices HTTP/1.1 Content-Type: application/json { "devices": [ "QBUAZIFURK", "AUIECTSRND" ], "auth": { "type": "example.type.foo", "session": "xxxxx", "example_credential": "verypoorsharedsecret" } }
Responses:
Status code 200:
The devices were successfully removed, or had been removed previously.
Example
{}
Status code 401:
The homeserver requires additional authentication information.
Example
{ "flows": [ { "stages": [ "example.type.foo" ] } ], "params": { "example.type.baz": { "example_key": "foobar" } }, "session": "xxxxxxyz", "completed": [ "example.type.foo" ] }
Deleting devices has security implications: it invalidates the access_token assigned to the device, so an attacker could use it to log out the real user (and do it repeatedly every time the real user tries to log in to block the attacker). Servers should require additional authentication beyond the access token when deleting devices (for example, requiring that the user resubmit their password).
The display names of devices are publicly visible. Clients should consider advising the user of this.
Matrix optionally supports end-to-end encryption, allowing rooms to be created whose conversation contents are not decryptable or interceptable on any of the participating homeservers.
Encryption and Authentication in Matrix is based around public-key cryptography. The Matrix protocol provides a basic mechanism for exchange of public keys, though an out-of-band channel is required to exchange fingerprints between users to build a web of trust.
1) Bob publishes the public keys and supported algorithms for his device. This may include long-term identity keys, and/or one-time keys. +----------+ +--------------+ | Bob's HS | | Bob's Device | +----------+ +--------------+ | | |<=============| /keys/upload 2) Alice requests Bob's public identity keys and supported algorithms. +----------------+ +------------+ +----------+ | Alice's Device | | Alice's HS | | Bob's HS | +----------------+ +------------+ +----------+ | | | |=================>|==============>| /keys/query <federation> 3) Alice selects an algorithm and claims any one-time keys needed. +----------------+ +------------+ +----------+ | Alice's Device | | Alice's HS | | Bob's HS | +----------------+ +------------+ +----------+ | | | |=================>|==============>| /keys/claim <federation>
The name ed25519 corresponds to the Ed25519 signature algorithm. The key is a 32-byte Ed25519 public key, encoded using unpadded Base64. Example:
"SogYyrkTldLz0BXP+GYWs0qaYacUI0RleEqNT8J3riQ"
The name curve25519 corresponds to the Curve25519 ECDH algorithm. The key is a 32-byte Curve25519 public key, encoded using unpadded Base64. Example:
"JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y"
The name signed_curve25519 also corresponds to the Curve25519 algorithm, but keys using this algorithm are objects with the properties key (giving the Base64-encoded 32-byte Curve25519 public key), and signatures (giving a signature for the key object, as described in Signing JSON). Example:
{ "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", "signatures": { "@user:example.com": { "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" } } }
Each device should have one Ed25519 signing key. This key should be generated on the device from a cryptographically secure source, and the private part of the key should never be exported from the device. This key is used as the fingerprint for a device by other clients.
A device will generally need to generate a number of additional keys. Details of these will vary depending on the messaging algorithm in use.
Algorithms generally require device identity keys as well as signing keys. Some algorithms also require one-time keys to improve their secrecy and deniability. These keys are used once during session establishment, and are then thrown away.
For Olm version 1, each device requires a single Curve25519 identity key, and a number of signed Curve25519 one-time keys.
A device uploads the public parts of identity keys to their homeserver as a signed JSON object, using the /keys/upload API. The JSON object must include the public part of the device's Ed25519 key, and must be signed by that key, as described in Signing JSON.
One-time keys are also uploaded to the homeserver using the /keys/upload API.
Devices must store the private part of each key they upload. They can discard the private part of a one-time key when they receive a message using that key. However it's possible that a one-time key given out by a homeserver will never be used, so the device that generates the key will never know that it can discard the key. Therefore a device could end up trying to store too many private keys. A device that is trying to store too many private keys may discard keys starting with the oldest.
Before Alice can send an encrypted message to Bob, she needs a list of each of his devices and the associated identity keys, so that she can establish an encryption session with each device. This list can be obtained by calling /keys/query, passing Bob's user ID in the device_keys parameter.
From time to time, Bob may add new devices, and Alice will need to know this so that she can include his new devices for later encrypted messages. A naive solution to this would be to call /keys/query before sending each message - however, the number of users and devices may be large and this would be inefficient.
It is therefore expected that each client will maintain a list of devices for a number of users (in practice, typically each user with whom we share an encrypted room). Furthermore, it is likely that this list will need to be persisted between invocations of the client application (to preserve device verification data and to alert Alice if Bob suddenly gets a new device).
Alice's client can maintain a list of Bob's devices via the following process:
Warning
Bob may update one of his devices while Alice has a request to /keys/query in flight. Alice's client may therefore see Bob's user ID in the device_lists field of the /sync response while the first request is in flight, and initiate a second request to /keys/query. This may lead to either of two related problems.
The first problem is that, when the first request completes, the client will clear the 'outdated' flag for Bob's devices. If the second request fails, or the client is shut down before it completes, this could lead to Alice using an outdated list of Bob's devices.
The second possibility is that, under certain conditions, the second request may complete before the first one. When the first request completes, the client could overwrite the later results from the second request with those from the first request.
Clients MUST guard against these situations. For example, a client could ensure that only one request to /keys/query is in flight at a time for each user, by queuing additional requests until the first completes. Alternatively, the client could make a new request immediately, but ensure that the first request's results are ignored (possibly by cancelling the request).
Note
When Bob and Alice share a room, with Bob tracking Alice's devices, she may leave the room and then add a new device. Bob will not be notified of this change, as he doesn't share a room anymore with Alice. When they start sharing a room again, Bob has an out-of-date list of Alice's devices. In order to address this issue, Bob's homeserver will add Alice's user ID to the changed property of the device_lists field, thus Bob will update his list of Alice's devices as part of his normal processing. Note that Bob can also be notified when he stops sharing any room with Alice by inspecting the left property of the device_lists field, and as a result should remove her from its list of tracked users.
When encryption is enabled in a room, files should be uploaded encrypted on the homeserver.
In order to achieve this, a client should generate a single-use 256-bit AES key, and encrypt the file using AES-CTR. The counter should be 64-bit long, starting at 0 and prefixed by a random 64-bit Initialization Vector (IV), which together form a 128-bit unique counter block.
Warning
An IV must never be used multiple times with the same key. This implies that if there are multiple files to encrypt in the same message, typically an image and its thumbnail, the files must not share both the same key and IV.
Then, the encrypted file can be uploaded to the homeserver. The key and the IV must be included in the room event along with the resulting mxc:// in order to allow recipients to decrypt the file. As the event containing those will be Megolm encrypted, the server will never have access to the decrypted file.
A hash of the ciphertext must also be included, in order to prevent the homeserver from changing the file content.
A client should send the data as an encrypted m.room.message event, using either m.file as the msgtype, or the appropriate msgtype for the file type. The key is sent using the JSON Web Key format, with a W3C extension.
This module adds file and thumbnail_file properties, of type EncryptedFile, to m.message msgtypes that reference files, such as m.file and m.image, replacing the url and thumbnail_url properties.
EncryptedFile
| Parameter | Type | Description |
|---|---|---|
| url | string | Required. The URL to the file. |
| key | JWK | Required. A JSON Web Key object. |
| iv | string | Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64. |
| hashes | {string: string} | Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. Clients should support the SHA-256 hash, which uses the key sha256. |
| v | string | Required. Version of the encrypted attachments protocol. Must be v2. |
JWK
| Parameter | Type | Description |
|---|---|---|
| kty | string | Required. Key type. Must be oct. |
| key_ops | [string] | Required. Key operations. Must at least contain encrypt and decrypt. |
| alg | string | Required. Algorithm. Must be A256CTR. |
| k | string | Required. The key, encoded as urlsafe unpadded base64. |
| ext | boolean | Required. Extractable. Must be true. This is a W3C extension. |
Example:
{ "content": { "body": "something-important.jpg", "file": { "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", "mimetype": "image/jpeg", "v": "v2", "key": { "alg": "A256CTR", "ext": true, "k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0", "key_ops": ["encrypt","decrypt"], "kty": "oct" }, "iv": "w+sE15fzSc0AAAAAAAAAAA", "hashes": { "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" } }, "info": { "mimetype": "image/jpeg", "h": 1536, "size": 422018, "thumbnail_file": { "hashes": { "sha256": "/NogKqW5bz/m8xHgFiH5haFGjCNVmUIPLzfvOhHdrxY" }, "iv": "U+k7PfwLr6UAAAAAAAAAAA", "key": { "alg": "A256CTR", "ext": true, "k": "RMyd6zhlbifsACM1DXkCbioZ2u0SywGljTH8JmGcylg", "key_ops": ["encrypt", "decrypt"], "kty": "oct" }, "mimetype": "image/jpeg", "url": "mxc://example.org/pmVJxyxGlmxHposwVSlOaEOv", "v": "v2" }, "thumbnail_info": { "h": 768, "mimetype": "image/jpeg", "size": 211009, "w": 432 }, "w": 864 }, "msgtype": "m.image" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } }
A client wanting to set up a session with another device can claim a one-time key for that device. This is done by making a request to the /keys/claim API.
A homeserver should rate-limit the number of one-time keys that a given user or remote server can claim. A homeserver should discard the public part of a one time key once it has given that key to another user.
Before Alice sends Bob encrypted data, or trusts data received from him, she may want to verify that she is actually communicating with him, rather than a man-in-the-middle. This verification process requires an out-of-band channel: there is no way to do it within Matrix without trusting the administrators of the homeservers.
In Matrix, verification works by Alice meeting Bob in person, or contacting him via some other trusted medium, and use SAS Verification to interactively verify Bob's devices. Alice and Bob may also read aloud their unpadded base64 encoded Ed25519 public key, as returned by /keys/query.
Device verification may reach one of several conclusions. For example:
Note
Once the signing key has been verified, it is then up to the encryption protocol to verify that a given message was sent from a device holding that Ed25519 private key, or to encrypt a message so that it may only be decrypted by such a device. For the Olm protocol, this is documented at https://matrix.org/docs/olm_signing.html.
Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors. In order to help mitigate errors, and to make the process easier for users, some verification methods are supported by the specification. The methods all use a common framework for negotiating the key verification.
To use this framework, Alice's client would send m.key.verification.request events to Bob's devices. All of the to_device messages sent to Bob MUST have the same transaction_id to indicate they are part of the same request. This allows Bob to reject the request on one device, and have it apply to all of his devices. Similarly, it allows Bob to process the verification on one device without having to involve all of his devices.
When Bob's device receives a m.key.verification.request, it should prompt Bob to verify keys with Alice using one of the supported methods in the request. If Bob's device does not understand any of the methods, it should not cancel the request as one of his other devices may support the request. Instead, Bob's device should tell Bob that an unsupported method was used for starting key verification. The prompt for Bob to accept/reject Alice's request (or the unsupported method prompt) should be automatically dismissed 10 minutes after the timestamp field or 2 minutes after Bob's client receives the message, whichever comes first, if Bob does not interact with the prompt. The prompt should additionally be hidden if an appropriate m.key.verification.cancel message is received.
If Bob rejects the request, Bob's client must send a m.key.verification.cancel message to Alice's device. Upon receipt, Alice's device should tell her that Bob does not want to verify her device and send m.key.verification.cancel messages to all of Bob's devices to notify them that the request was rejected.
If Bob accepts the request, Bob's device starts the key verification process by sending a m.key.verification.start message to Alice's device. Upon receipt of this message, Alice's device should send a m.key.verification.cancel message to all of Bob's other devices to indicate the process has been started. The start message must use the same transaction_id from the original key verification request if it is in response to the request. The start message can be sent indepdently of any request.
Individual verification methods may add additional steps, events, and properties to the verification messages. Event types for methods defined in this specification must be under the m.key.verification namespace and any other event types must be namespaced according to the Java package naming convention.
Any of Alice's or Bob's devices can cancel the key verification request or process at any time with a m.key.verification.cancel message to all applicable devices.
This framework yields the following handshake, assuming both Alice and Bob each have 2 devices, Bob's first device accepts the key verification request, and Alice's second device initiates the request. Note how Alice's first device is not involved in the request or verification process.
+---------------+ +---------------+ +-------------+ +-------------+ | AliceDevice1 | | AliceDevice2 | | BobDevice1 | | BobDevice2 | +---------------+ +---------------+ +-------------+ +-------------+ | | | | | | m.key.verification.request | | | |---------------------------------->| | | | | | | | m.key.verification.request | | | |-------------------------------------------------->| | | | | | | m.key.verification.start | | | |<----------------------------------| | | | | | | | m.key.verification.cancel | | | |-------------------------------------------------->| | | | |
After the handshake, the verification process begins.
Requests a key verification with another user's devices. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| from_device | string | Required. The device ID which is initiating the request. |
| transaction_id | string | Required. An opaque identifier for the verification request. Must be unique with respect to the devices involved. |
| methods | [string] | Required. The verification methods supported by the sender. |
| timestamp | integer | Required. The POSIX timestamp in milliseconds for when the request was made. If the request is in the future by more than 5 minutes or more than 10 minutes in the past, the message should be ignored by the receiver. |
Example:
{ "content": { "from_device": "AliceDevice2", "methods": [ "m.sas.v1" ], "timestamp":<PHONE_NUMBER>869, "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.request" }
Begins a key verification process. Typically sent as a to-device event. The method field determines the type of verification. The fields in the event will differ depending on the method. This definition includes fields that are in common among all variants.
| Content Key | Type | Description |
|---|---|---|
| from_device | string | Required. The device ID which is initiating the process. |
| transaction_id | string | Required. An opaque identifier for the verification process. Must be unique with respect to the devices involved. Must be the same as the transaction_id given in the m.key.verification.request if this process is originating from a request. |
| method | string | Required. The verification method to use. |
| next_method | string | Optional method to use to verify the other user's key with. Applicable when the method chosen only verifies one user's key. This field will never be present if the method verifies keys both ways. |
Examples:
{ "content": { "from_device": "BobDevice1", "method": "m.sas.v1", "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.start" }
{ "content": { "from_device": "BobDevice1", "hashes": [ "sha256" ], "key_agreement_protocols": [ "curve25519" ], "message_authentication_codes": [ "hkdf-hmac-sha256" ], "method": "m.sas.v1", "short_authentication_string": [ "decimal", "emoji" ], "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.start" }
Cancels a key verification process/request. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| transaction_id | string | Required. The opaque identifier for the verification process/request. |
| reason | string | Required. A human readable description of the code. The client should only rely on this string if it does not understand the code. |
| code | string | Required. The error code for why the process/request was cancelled by the user. Error codes should use the Java package naming convention if not in the following list: m.user: The user cancelled the verification. m.timeout: The verification process timed out. Verification processes can define their own timeout parameters. m.unknown_transaction: The device does not know about the given transaction ID. m.unknown_method: The device does not know how to handle the requested method. This should be sent for m.key.verification.start messages and messages defined by individual verification processes. m.unexpected_message: The device received an unexpected message. Typically raised when one of the parties is handling the verification out of order. m.key_mismatch: The key was not verified. m.user_mismatch: The expected user did not match the user verified. m.invalid_message: The message received was invalid. m.accepted: A m.key.verification.request was accepted by a different device. The device receiving this error can ignore the verification request. Clients should be careful to avoid error loops. For example, if a device sends an incorrect message and the client returns m.invalid_message to which it gets an unexpected response with m.unexpected_message, the client should not respond again with m.unexpected_message to avoid the other device potentially sending another error response. |
Example:
{ "content": { "code": "m.user", "reason": "User rejected the key verification request", "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.cancel" }
SAS verification is a user-friendly key verification process built off the common framework outlined above. SAS verification is intended to be a highly interactive process for users, and as such exposes verfiication methods which are easier for users to use.
The verification process is heavily inspired by Phil Zimmerman's ZRTP key agreement handshake. A key part of key agreement in ZRTP is the hash commitment: the party that begins the Diffie-Hellman key sharing sends a hash of their part of the Diffie-Hellman exchange, and does not send their part of the Diffie-Hellman exchange until they have received the other party's part. Thus an attacker essentially only has one attempt to attack the Diffie-Hellman exchange, and hence we can verify fewer bits while still achieving a high degree of security: if we verify n bits, then an attacker has a 1 in 2n chance of success. For example, if we verify 40 bits, then an attacker has a 1 in 1,099,511,627,776 chance (or less than 1 in 1012 chance) of success. A failed attack would result in a mismatched Short Authentication String, alerting users to the attack.
The verification process takes place over to-device messages in two phases:
The process between Alice and Bob verifying each other would be:
The wire protocol looks like the following between Alice and Bob's devices:
+-------------+ +-----------+ | AliceDevice | | BobDevice | +-------------+ +-----------+ | | | m.key.verification.start | |-------------------------------->| | | | m.key.verification.accept | |<--------------------------------| | | | m.key.verification.key | |-------------------------------->| | | | m.key.verification.key | |<--------------------------------| | | | m.key.verification.mac | |-------------------------------->| | | | m.key.verification.mac | |<--------------------------------| | |
At any point the interactive verfication can go wrong. The following describes what to do when an error happens:
Building off the common framework, the following events are involved in SAS verification.
The m.key.verification.cancel event is unchanged, however the following error codes are used in addition to those already specified:
Begins a SAS key verification process using the m.sas.v1 method. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| from_device | string | Required. The device ID which is initiating the process. |
| transaction_id | string | Required. An opaque identifier for the verification process. Must be unique with respect to the devices involved. Must be the same as the transaction_id given in the m.key.verification.request if this process is originating from a request. |
| method | enum | Required. The verification method to use. Must be 'm.sas.v1'. |
| key_agreement_protocols | [string] | Required. The key agreement protocols the sending device understands. Must include at least curve25519. |
| hashes | [string] | Required. The hash methods the sending device understands. Must include at least sha256. |
| message_authentication_codes | [string] | Required. The message authentication codes that the sending device understands. Must include at least hkdf-hmac-sha256. |
| short_authentication_string | [enum] | Required. The SAS methods the sending device (and the sending device's user) understands. Must include at least decimal. Optionally can include emoji. One of: ["decimal", "emoji"] |
Example:
{ "content": { "from_device": "BobDevice1", "hashes": [ "sha256" ], "key_agreement_protocols": [ "curve25519" ], "message_authentication_codes": [ "hkdf-hmac-sha256" ], "method": "m.sas.v1", "short_authentication_string": [ "decimal", "emoji" ], "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.start" }
Accepts a previously sent m.key.verification.start message. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| transaction_id | string | Required. An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. |
| method | enum | Required. The verification method to use. Must be 'm.sas.v1'. |
| key_agreement_protocol | string | Required. The key agreement protocol the device is choosing to use, out of the options in the m.key.verification.start message. |
| hash | string | Required. The hash method the device is choosing to use, out of the options in the m.key.verification.start message. |
| message_authentication_code | string | Required. The message authentication code the device is choosing to use, out of the options in the m.key.verification.start message. |
| short_authentication_string | [enum] | Required. The SAS methods both devices involved in the verification process understand. Must be a subset of the options in the m.key.verification.start message. One of: ["decimal", "emoji"] |
| commitment | string | Required. The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public key (encoded as unpadded base64) and the canonical JSON representation of the m.key.verification.start message. |
Example:
{ "content": { "commitment": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg", "hash": "sha256", "key_agreement_protocol": "curve25519", "message_authentication_code": "hkdf-hmac-sha256", "method": "m.sas.v1", "short_authentication_string": [ "decimal", "emoji" ], "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.accept" }
Sends the ephemeral public key for a device to the partner device. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| transaction_id | string | Required. An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. |
| key | string | Required. The device's ephemeral public key, encoded as unpadded base64. |
Example:
{ "content": { "key": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg", "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.key" }
Sends the MAC of a device's key to the partner device. Typically sent as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| transaction_id | string | Required. An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. |
| mac | {string: string} | Required. A map of the key ID to the MAC of the key, using the algorithm in the verification process. The MAC is encoded as unpadded base64. |
| keys | string | Required. The MAC of the comma-separated, sorted, list of key IDs given in the mac property, encoded as unpadded base64. |
Example:
{ "content": { "keys": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA", "mac": { "ed25519:ABCDEF": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" }, "transaction_id": "S0meUniqueAndOpaqueString" }, "type": "m.key.verification.mac" }
In all of the SAS methods, HKDF is as defined in [RFC 5869](https://tools.ietf.org/html/rfc5869) and uses the previously agreed-upon hash function for the hash function. The shared secret is supplied as the input keying material. No salt is used, and the input parameter is the concatenation of:
- The string MATRIX_KEY_VERIFICATION_SAS.
- The Matrix ID of the user who sent the m.key.verification.start message.
- The Device ID of the device which sent the m.key.verification.start message.
- The Matrix ID of the user who sent the m.key.verification.accept message.
- The Device ID of the device which sent the m.key.verification.accept message.
- The transaction_id being used.
Rationale
HKDF is used over the plain shared secret as it results in a harder attack as well as more uniform data to work with.
For verification of each party's device keys, HKDF is as defined in RFC 5869 and uses SHA-256 as the hash function. The shared secret is supplied as the input keying material. No salt is used, and in the input parameter is the concatenation of:
- The string MATRIX_KEY_VERIFICATION_MAC.
- The Matrix ID of the user whose key is being MAC-ed.
- The Device ID of the device sending the MAC.
- The Matrix ID of the other user.
- The Device ID of the device receiving the MAC.
- The transaction_id being used.
- The Key ID of the key being MAC-ed, or the string KEY_IDS if the item being MAC-ed is the list of key IDs.
Generate 5 bytes using HKDF then take sequences of 13 bits to convert to decimal numbers (resulting in 3 numbers between 0 and 8191 inclusive each). Add 1000 to each calculated number.
The bitwise operations to get the numbers given the 5 bytes B0, B1, B2, B3, B4 would be:
The digits are displayed to the user either with an appropriate separator, such as dashes, or with the numbers on individual lines.
Generate 6 bytes using HKDF then split the first 42 bits into 7 groups of 6 bits, similar to how one would base64 encode something. Convert each group of 6 bits to a number and use the following table to get the corresponding emoji:
| Number | Emoji | Unicode | Description |
|---|---|---|---|
| 0 | 🐶 | U+1F436 | Dog |
| 1 | 🐱 | U+1F431 | Cat |
| 2 | 🦁 | U+1F981 | Lion |
| 3 | 🐎 | U+1F40E | Horse |
| 4 | 🦄 | U+1F984 | Unicorn |
| 5 | 🐷 | U+1F437 | Pig |
| 6 | 🐘 | U+1F418 | Elephant |
| 7 | 🐰 | U+1F430 | Rabbit |
| 8 | 🐼 | U+1F43C | Panda |
| 9 | 🐓 | U+1F413 | Rooster |
| 10 | 🐧 | U+1F427 | Penguin |
| 11 | 🐢 | U+1F422 | Turtle |
| 12 | 🐟 | U+1F41F | Fish |
| 13 | 🐙 | U+1F419 | Octopus |
| 14 | 🦋 | U+1F98B | Butterfly |
| 15 | 🌷 | U+1F337 | Flower |
| 16 | 🌳 | U+1F333 | Tree |
| 17 | 🌵 | U+1F335 | Cactus |
| 18 | 🍄 | U+1F344 | Mushroom |
| 19 | 🌏 | U+1F30F | Globe |
| 20 | 🌙 | U+1F319 | Moon |
| 21 | ☁ | U+2601U+FE0F | Cloud |
| 22 | 🔥 | U+1F525 | Fire |
| 23 | 🍌 | U+1F34C | Banana |
| 24 | 🍎 | U+1F34E | Apple |
| 25 | 🍓 | U+1F353 | Strawberry |
| 26 | 🌽 | U+1F33D | Corn |
| 27 | 🍕 | U+1F355 | Pizza |
| 28 | 🎂 | U+1F382 | Cake |
| 29 | ❤ | U+2764U+FE0F | Heart |
| 30 | 😀 | U+1F600 | Smiley |
| 31 | 🤖 | U+1F916 | Robot |
| 32 | 🎩 | U+1F3A9 | Hat |
| 33 | 👓 | U+1F453 | Glasses |
| 34 | 🔧 | U+1F527 | Spanner |
| 35 | 🎅 | U+1F385 | Santa |
| 36 | 👍 | U+1F44D | Thumbs Up |
| 37 | ☂ | U+2602U+FE0F | Umbrella |
| 38 | ⌛ | U+231B | Hourglass |
| 39 | ⏰ | U+23F0 | Clock |
| 40 | 🎁 | U+1F381 | Gift |
| 41 | 💡 | U+1F4A1 | Light Bulb |
| 42 | 📕 | U+1F4D5 | Book |
| 43 | ✏ | U+270FU+FE0F | Pencil |
| 44 | 📎 | U+1F4CE | Paperclip |
| 45 | ✂ | U+2702U+FE0F | Scissors |
| 46 | 🔒 | U+1F512 | Lock |
| 47 | 🔑 | U+1F511 | Key |
| 48 | 🔨 | U+1F528 | Hammer |
| 49 | ☎ | U+260EU+FE0F | Telephone |
| 50 | 🏁 | U+1F3C1 | Flag |
| 51 | 🚂 | U+1F682 | Train |
| 52 | 🚲 | U+1F6B2 | Bicycle |
| 53 | ✈ | U+2708U+FE0F | Aeroplane |
| 54 | 🚀 | U+1F680 | Rocket |
| 55 | 🏆 | U+1F3C6 | Trophy |
| 56 | ⚽ | U+26BD | Ball |
| 57 | 🎸 | U+1F3B8 | Guitar |
| 58 | 🎺 | U+1F3BA | Trumpet |
| 59 | 🔔 | U+1F514 | Bell |
| 60 | ⚓ | U+2693 | Anchor |
| 61 | 🎧 | U+1F3A7 | Headphones |
| 62 | 📁 | U+1F4C1 | Folder |
| 63 | 📌 | U+1F4CC | Pin |
Note
This table is available as JSON at https://github.com/matrix-org/matrix-doc/blob/master/data-definitions/sas-emoji.json
Rationale
The emoji above were chosen to:
Clients SHOULD show the emoji with the descriptions from the table, or appropriate translation of those descriptions. Client authors SHOULD collaborate to create a common set of translations for all languages.
If Bob has an encrypted conversation with Alice on his computer, and then logs in through his phone for the first time, he may want to have access to the previously exchanged messages. To address this issue, several methods are provided to allow users to transfer keys from one device to another.
When a device is missing keys to decrypt messages, it can request the keys by sending m.room_key_request to-device messages to other devices with action set to request. If a device wishes to share the keys with that device, it can forward the keys to the first device by sending an encrypted m.forwarded_room_key to-device message. The first device should then send an m.room_key_request to-device message with action set to request_cancellation to the other devices that it had originally sent the key request to; a device that receives a request_cancellation should disregard any previously-received request message with the same request_id and requesting_device_id.
Note
Key sharing can be a big attack vector, thus it must be done very carefully. A reasonable strategy is for a user's client to only send keys requested by the verified devices of the same user.
Keys can be manually exported from one device to an encrypted file, copied to another device, and imported. The file is encrypted using a user-supplied passphrase, and is created as follows:
Encode the sessions as a JSON object, formatted as described in Key export format.
Generate a 512-bit key from the user-entered passphrase by computing PBKDF2(HMAC-SHA-512, passphrase, S, N, 512), where S is a 128-bit cryptographically-random salt and N is the number of rounds. N should be at least 100,000. The keys K and K' are set to the first and last 256 bits of this generated key, respectively. K is used as an AES-256 key, and K' is used as an HMAC-SHA-256 key.
Serialize the JSON object as a UTF-8 string, and encrypt it using AES-CTR-256 with the key K generated above, and with a 128-bit cryptographically-random initialization vector, IV, that has bit 63 set to zero. (Setting bit 63 to zero in IV is needed to work around differences in implementations of AES-CTR.)
Concatenate the following data:
| Size (bytes) | Description |
|---|---|
| 1 | Export format version, which must be 0x01. |
| 16 | The salt S. |
| 16 | The initialization vector IV. |
| 4 | The number of rounds N, as a big-endian unsigned 32-bit integer. |
| variable | The encrypted JSON object. |
| 32 | The HMAC-SHA-256 of all the above string concatenated together, using K' as the key. |
Base64-encode the string above. Newlines may be added to avoid overly long lines.
Prepend the resulting string with -----BEGIN MEGOLM SESSION DATA-----, with a trailing newline, and append -----END MEGOLM SESSION DATA-----, with a leading and trailing newline.
The exported sessions are formatted as a JSON array of SessionData objects described as follows:
SessionData
| Parameter | Type | Description |
|---|---|---|
| algorithm | string | Required. The encryption algorithm that the session uses. Must be m.megolm.v1.aes-sha2. |
| forwarding_curve25519_key_chain | [string] | Required. Chain of Curve25519 keys through which this session was forwarded, via m.forwarded_room_key events. |
| room_id | string | Required. The room where the session is used. |
| sender_key | string | Required. The Curve25519 key of the device which initiated the session originally. |
| sender_claimed_keys | {string: string} | Required. The Ed25519 key of the device which initiated the session originally. |
| session_id | string | Required. The ID of the session. |
| session_key | string | Required. The key for the session. |
Example:
{ "sessions": [ { "algorithm": "m.megolm.v1.aes-sha2", "forwarding_curve25519_key_chain": [ "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" ], "room_id": "!Cuyf34gef24t:localhost", "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", "sender_claimed_keys": { "ed25519": "<device ed25519 identity key>", }, "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." }, ... ] }
Messaging algorithm names use the extensible naming scheme used throughout this specification. Algorithm names that start with m. are reserved for algorithms defined by this specification. Implementations wanting to experiment with new algorithms must be uniquely globally namespaced following Java's package naming conventions.
Algorithm names should be short and meaningful, and should list the primitives used by the algorithm so that it is easier to see if the algorithm is using a broken primitive.
A name of m.olm.v1 is too short: it gives no information about the primitives in use, and is difficult to extend for different primitives. However a name of m.olm.v1.ecdh-curve25519-hdkfsha256.hmacsha256.hkdfsha256-aes256-cbc-hmac64sha256 is too long despite giving a more precise description of the algorithm: it adds to the data transfer overhead and sacrifices clarity for human readers without adding any useful extra information.
The name m.olm.v1.curve25519-aes-sha2 corresponds to version 1 of the Olm ratchet, as defined by the Olm specification. This uses:
Devices that support Olm must include "m.olm.v1.curve25519-aes-sha2" in their list of supported messaging algorithms, must list a Curve25519 device key, and must publish Curve25519 one-time keys.
An event encrypted using Olm has the following format:
{ "type": "m.room.encrypted", "content": { "algorithm": "m.olm.v1.curve25519-aes-sha2", "sender_key": "<sender_curve25519_key>", "ciphertext": { "<device_curve25519_key>": { "type": 0, "body": "<encrypted_payload_base_64>" } } } }
ciphertext is a mapping from device Curve25519 key to an encrypted payload for that device. body is a Base64-encoded Olm message body. type is an integer indicating the type of the message body: 0 for the initial pre-key message, 1 for ordinary messages.
Olm sessions will generate messages with a type of 0 until they receive a message. Once a session has decrypted a message it will produce messages with a type of 1.
When a client receives a message with a type of 0 it must first check if it already has a matching session. If it does then it will use that session to try to decrypt the message. If there is no existing session then the client must create a new session and use the new session to decrypt the message. A client must not persist a session or remove one-time keys used by a session until it has successfully decrypted a message using that session.
Messages with type 1 can only be decrypted with an existing session. If there is no matching session, the client must treat this as an invalid message.
The plaintext payload is of the form:
{ "type": "<type of the plaintext event>", "content": "<content for the plaintext event>", "sender": "<sender_user_id>", "recipient": "<recipient_user_id>", "recipient_keys": { "ed25519": "<our_ed25519_key>" }, "keys": { "ed25519": "<sender_ed25519_key>" } }
The type and content of the plaintext message event are given in the payload.
Other properties are included in order to prevent an attacker from publishing someone else's curve25519 keys as their own and subsequently claiming to have sent messages which they didn't. sender must correspond to the user who sent the event, recipient to the local user, and recipient_keys to the local ed25519 key.
Clients must confirm that the sender_key and the ed25519 field value under the keys property match the keys returned by /keys/query for the given user, and must also verify the signature of the payload. Without this check, a client cannot be sure that the sender device owns the private part of the ed25519 key it claims to have in the Olm payload. This is crucial when the ed25519 key corresponds to a verified device.
If a client has multiple sessions established with another device, it should use the session from which it last received and successfully decrypted a message. For these purposes, a session that has not received any messages should use its creation time as the time that it last received a message. A client may expire old sessions by defining a maximum number of olm sessions that it will maintain for each device, and expiring sessions on a Least Recently Used basis. The maximum number of olm sessions maintained per device should be at least 4.
Occasionally messages may be undecryptable by clients due to a variety of reasons. When this happens to an Olm-encrypted message, the client should assume that the Olm session has become corrupted and create a new one to replace it.
Note
Megolm-encrypted messages generally do not have the same problem. Usually the key for an undecryptable Megolm-encrypted message will come later, allowing the client to decrypt it successfully. Olm does not have a way to recover from the failure, making this session replacement process required.
To establish a new session, the client sends a m.dummy to-device event to the other party to notify them of the new session details.
Clients should rate-limit the number of sessions it creates per device that it receives a message from. Clients should not create a new session with another device if it has already created one for that given device in the past 1 hour.
Clients should attempt to mitigate loss of the undecryptable messages. For example, Megolm sessions that were sent using the old session would have been lost. The client can attempt to retrieve the lost sessions through m.room_key_request messages.
The name m.megolm.v1.aes-sha2 corresponds to version 1 of the Megolm ratchet, as defined by the Megolm specification. This uses:
Devices that support Megolm must support Olm, and include "m.megolm.v1.aes-sha2" in their list of supported messaging algorithms.
An event encrypted using Megolm has the following format:
{ "type": "m.room.encrypted", "content": { "algorithm": "m.megolm.v1.aes-sha2", "sender_key": "<sender_curve25519_key>", "device_id": "<sender_device_id>", "session_id": "<outbound_group_session_id>", "ciphertext": "<encrypted_payload_base_64>" } }
The encrypted payload can contain any message event. The plaintext is of the form:
{ "type": "<event_type>", "content": "<event_content>", "room_id": "<the room_id>" }
We include the room ID in the payload, because otherwise the homeserver would be able to change the room a message was sent in.
Clients must guard against replay attacks by keeping track of the ratchet indices of Megolm sessions. They should reject messages with a ratchet index that they have already decrypted. Care should be taken in order to avoid false positives, as a client may decrypt the same event twice as part of its normal processing.
As with Olm events, clients must confirm that the sender_key belongs to the user who sent the message. The same reasoning applies, but the sender ed25519 key has to be inferred from the keys.ed25519 property of the event which established the Megolm session.
In order to enable end-to-end encryption in a room, clients can send a m.room.encryption state event specifying m.megolm.v1.aes-sha2 as its algorithm property.
When creating a Megolm session in a room, clients must share the corresponding session key using Olm with the intended recipients, so that they can decrypt future messages encrypted using this session. A m.room_key event is used to do this. Clients must also handle m.room_key events sent by other devices in order to decrypt their messages.
Defines how messages sent in this room should be encrypted.
| Content Key | Type | Description |
|---|---|---|
| algorithm | enum | Required. The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'. |
| rotation_period_ms | integer | How long the session should be used before changing it. 604800000 (a week) is the recommended default. |
| rotation_period_msgs | integer | How many messages should be sent before changing the session. 100 is the recommended default. |
Example:
{ "content": { "algorithm": "m.megolm.v1.aes-sha2", "rotation_period_ms": 604800000, "rotation_period_msgs": 100 }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts":<PHONE_NUMBER>653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "state_key": "", "type": "m.room.encryption", "unsigned": { "age": 1234 } }
This event type is used when sending encrypted events. It can be used either within a room (in which case it will have all of the Room Event fields), or as a to-device event.
| Content Key | Type | Description |
|---|---|---|
| algorithm | enum | Required. The encryption algorithm used to encrypt this event. The value of this field determines which other properties will be present. One of: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"] |