---
title: "Server-Server API"
weight: 20
type: docs
description: |
  Matrix homeservers use the Federation APIs (also known as server-server APIs)
  to communicate with each other. Homeservers use these APIs to push messages in
  real-time, retrieve historic messages, and query profile or presence
  information about users on other servers. The APIs are implemented over HTTPS,
  with authentication provided by public key signatures both at the TLS
  transport layer and in HTTP `Authorization` headers.

  There are three main kinds of communication that occur between
  homeservers:

  Persistent Data Units (PDUs):
  These events are broadcast from one homeserver to any others that have
  joined the same room (identified by Room ID). They are persisted in
  long-term storage and record the history of messages and state for a
  room.

  Like email, it is the responsibility of the originating server of a PDU
  to deliver that event to its recipient servers. However PDUs are signed
  using the originating server's private key so that it is possible to
  deliver them through third-party servers.

  Ephemeral Data Units (EDUs):
  These events are pushed between pairs of homeservers. They are not
  persisted and are not part of the history of a room, nor does the
  receiving homeserver have to reply to them.

  Queries:
  These are single request/response interactions between a given pair of
  servers, initiated by one side sending an HTTPS GET request to obtain
  some information, and responded by the other. They are not persisted and
  contain no long-term significant history. They simply request a snapshot
  state at the instant the query is made.

  EDUs and PDUs are further wrapped in an envelope called a Transaction,
  which is transferred from the origin to the destination homeserver using
  an HTTPS PUT request.

---

## API standards

The mandatory baseline for server-server communication in Matrix is
exchanging JSON objects over HTTPS APIs. More efficient transports may be
specified in future as optional extensions.

All `POST` and `PUT` endpoints require the requesting server to supply a
request body containing a (potentially empty) JSON object. Requesting servers
should supply a `Content-Type` header of `application/json` for all requests
with JSON bodies, but this is not required.

Similarly, all endpoints in this specification require the destination server
to return a JSON object.  Servers must include a `Content-Type` header of
`application/json` for all JSON responses.

All JSON data, in requests or responses, must be encoded using UTF-8.

### TLS

Server-server communication must take place over HTTPS.

The destination server must provide a TLS certificate signed by a known
Certificate Authority.

Requesting servers are ultimately responsible for determining the trusted Certificate
Authorities, however are strongly encouraged to rely on the operating system's
judgement. Servers can offer administrators a means to override the trusted
authorities list.  Servers can additionally skip the certificate validation for
a given whitelist of domains or netmasks for the purposes of testing or in
networks where verification is done elsewhere, such as with `.onion`
addresses.

Servers should respect SNI when making requests where possible: a SNI should be
sent for the certificate which is expected, unless that certificate is expected
to be an IP address in which case SNI is not supported and should not be sent.

Servers are encouraged to make use of the [Certificate
Transparency](https://www.certificate-transparency.org/) project.

### Unsupported endpoints

If a request for an unsupported (or unknown) endpoint is received then the server
must respond with a 404 `M_UNRECOGNIZED` error.

Similarly, a 405 `M_UNRECOGNIZED` error is used to denote an unsupported method
to a known endpoint.

## Server discovery

### Resolving server names

Each Matrix homeserver is identified by a server name consisting of a
hostname and an optional port, as described by the
[grammar](/appendices#server-name). Where applicable, a delegated
server name uses the same grammar.

Server names are resolved to an IP address and port to connect to, and
have various conditions affecting which certificates and `Host` headers
to send. The process overall is as follows:

1.  If the hostname is an IP literal, then that IP address should be
    used, together with the given port number, or 8448 if no port is
    given. The target server must present a valid certificate for the IP
    address. The `Host` header in the request should be set to the
    server name, including the port if the server name included one.

2.  If the hostname is not an IP literal, and the server name includes an
    explicit port, resolve the hostname to an IP address using CNAME, AAAA or A
    records.
    Requests are made to the resolved IP address and given port with a
    `Host` header of the original server name (with port). The target
    server must present a valid certificate for the hostname.

3.  If the hostname is not an IP literal, a regular HTTPS request is
    made to `https://<hostname>/.well-known/matrix/server` (according to
    [RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615)), expecting
    the schema defined later in this section. 30x redirects should be
    followed, however redirection loops should be avoided. Responses
    (successful or otherwise) to the `/.well-known` endpoint should be
    cached by the requesting server. Servers should respect the cache
    control headers present on the response, or use a sensible default
    when headers are not present. The recommended sensible default is 24
    hours. Servers should additionally impose a maximum cache time for
    responses: 48 hours is recommended. Errors are recommended to be
    cached for up to an hour, and servers are encouraged to
    exponentially back off for repeated failures. The schema of the
    `/.well-known` request is later in this section. If the response is
    invalid (bad JSON, missing properties, non-200 response, etc), skip
    to step 4. If the response is valid, the `m.server` property is
    parsed as `<delegated_hostname>[:<delegated_port>]` and processed as
    follows:
    1.   If `<delegated_hostname>` is an IP literal, then that IP address
        should be used together with the `<delegated_port>` or 8448 if
        no port is provided. The target server must present a valid TLS
        certificate for the IP address. Requests must be made with a
        `Host` header containing the IP address, including the port if
        one was provided.
    2.   If `<delegated_hostname>` is not an IP literal, and
        `<delegated_port>` is present, an IP address is discovered by
        looking up CNAME, AAAA or A records for `<delegated_hostname>`.  The
        resulting IP address is used, alongside the `<delegated_port>`.
        Requests must be made with a `Host` header of
        `<delegated_hostname>:<delegated_port>`. The target server must
        present a valid certificate for `<delegated_hostname>`.
    3.  {{% added-in v="1.8" %}} If `<delegated_hostname>` is not an IP literal and no
        `<delegated_port>` is present, an SRV record is looked up for
        `_matrix-fed._tcp.<delegated_hostname>`. This may result in another
        hostname (to be resolved using AAAA or A records) and port.
        Requests should be made to the resolved IP address and port with
        a `Host` header containing the `<delegated_hostname>`. The
        target server must present a valid certificate for
        `<delegated_hostname>`.
    4.  **[Deprecated]** If `<delegated_hostname>` is not an IP literal, no
        `<delegated_port>` is present, and a `_matrix-fed._tcp.<delegated_hostname>`
        SRV record was not found, an SRV record is looked up for
        `_matrix._tcp.<delegated_hostname>`. This may result in another
        hostname (to be resolved using AAAA or A records) and port.
        Requests should be made to the resolved IP address and port with
        a `Host` header containing the `<delegated_hostname>`. The
        target server must present a valid certificate for
        `<delegated_hostname>`.
    5.   If no SRV record is found, an IP address is resolved using CNAME, AAAA
        or A records. Requests are then made to the resolved IP address
        and a port of 8448, using a `Host` header of
        `<delegated_hostname>`. The target server must present a valid
        certificate for `<delegated_hostname>`.

4.  {{% added-in v="1.8" %}} If the `/.well-known` request resulted in an error response, a server is
    found by resolving an SRV record for `_matrix-fed._tcp.<hostname>`. This may
    result in a hostname (to be resolved using AAAA or A records) and
    port. Requests are made to the resolved IP address and port, with a `Host`
    header of `<hostname>`. The target server must present a valid certificate
    for `<hostname>`.

5.  **[Deprecated]** If the `/.well-known` request resulted in an error response,
    and a `_matrix-fed._tcp.<hostname>` SRV record was not found, a server is
    found by resolving an SRV record for `_matrix._tcp.<hostname>`. This may
    result in a hostname (to be resolved using AAAA or A records) and
    port. Requests are made to the resolved IP address and port, with a `Host`
    header of `<hostname>`. The target server must present a valid certificate
    for `<hostname>`.

6.  If the `/.well-known` request returned an error response, and
    no SRV records were found, an IP address is resolved using CNAME, AAAA and A
    records. Requests are made to the resolved IP address using port
    8448 and a `Host` header containing the `<hostname>`. The target
    server must present a valid certificate for `<hostname>`.

{{% boxes/note %}}
The reasons we require `<hostname>` rather than `<delegated_hostname>` for SRV
delegation are:
  1. DNS is insecure (not all domains have DNSSEC), so the target of the delegation
      must prove that it is a valid delegate for `<hostname>` via TLS.
  2. Consistency with the recommendations in [RFC6125](https://datatracker.ietf.org/doc/html/rfc6125#section-6.2.1)
     and other applications using SRV records such [XMPP](https://datatracker.ietf.org/doc/html/rfc6120#section-13.7.2.1).
{{% /boxes/note %}}

{{% boxes/note %}}
Note that the target of a SRV record may *not* be a CNAME, as
mandated by [RFC2782](https://www.rfc-editor.org/rfc/rfc2782.html):

> the name MUST NOT be an alias (in the sense of RFC 1034 or RFC 2181)
{{% /boxes/note %}}

{{% boxes/note %}}
Steps 3.4 and 5 are deprecated because they use a service name not registered by IANA.
They may be removed in a future version of the specification. Server admins are encouraged
to use `.well-known` over any form of SRV records.

The IANA registration for port 8448 and `matrix-fed` can be found [here](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=matrix-fed).
{{% /boxes/note %}}

{{% http-api spec="server-server" api="wellknown" %}}

### Server implementation

{{% http-api spec="server-server" api="version" %}}

### Retrieving server keys

{{% boxes/note %}}
There was once a "version 1" of the key exchange. It has been removed
from the specification due to lack of significance. It may be reviewed
[from the historical
draft](https://github.com/matrix-org/matrix-doc/blob/51faf8ed2e4a63d4cfd6d23183698ed169956cc0/specification/server_server_api.rst#232version-1).
{{% /boxes/note %}}

Each homeserver publishes its public keys under
`/_matrix/key/v2/server`. Homeservers query for keys by either
getting `/_matrix/key/v2/server` directly or by querying an
intermediate notary server using a
`/_matrix/key/v2/query/{serverName}` API. Intermediate notary
servers query the `/_matrix/key/v2/server` API on behalf of
another server and sign the response with their own key. A server may
query multiple notary servers to ensure that they all report the same
public keys.

This approach is borrowed from the [Perspectives
Project](https://web.archive.org/web/20170702024706/https://perspectives-project.org/),
but modified to include the NACL keys and to use JSON instead of XML. It
has the advantage of avoiding a single trust-root since each server is
free to pick which notary servers they trust and can corroborate the
keys returned by a given notary server by querying other servers.

#### Publishing Keys

Homeservers publish their signing keys in a JSON object at
`/_matrix/key/v2/server`. The response contains a list of
`verify_keys` that are valid for signing federation requests made by the
homeserver and for signing events. It contains a list of
`old_verify_keys` which are only valid for signing events.

{{% http-api spec="server-server" api="keys_server" %}}

#### Querying Keys Through Another Server

Servers may query another server's keys through a notary server. The
notary server may be another homeserver. The notary server will retrieve
keys from the queried servers through use of the
`/_matrix/key/v2/server` API. The notary server will
additionally sign the response from the queried server before returning
the results.

Notary servers can return keys for servers that are offline or having
issues serving their own keys by using cached responses. Keys can be
queried from multiple servers to mitigate against DNS spoofing.

{{% http-api spec="server-server" api="keys_query" %}}

## Authentication

### Request Authentication

Every HTTP request made by a homeserver is authenticated using public
key digital signatures. The request method, target and body are signed
by wrapping them in a JSON object and signing it using the [JSON signing
algorithm](/appendices#signing-json). The resulting signatures are added
as an `Authorization` header with an auth scheme of `X-Matrix`. Note that
the target field should include the full path starting with `/_matrix/...`,
including the `?` and any query parameters if present, but should not
include the leading `https:`, nor the destination server's hostname.

Step 1 sign JSON:

```nohighlight
{
    "method": "POST",
    "uri": "/target",
    "origin": "origin.hs.example.com",
    "destination": "destination.hs.example.com",
    "content": <JSON-parsed request body>,
    "signatures": {
        "origin.hs.example.com": {
            "ed25519:key1": "ABCDEF..."
        }
    }
}
```

The server names in the JSON above are the server names for each
homeserver involved. Delegation from the [server name resolution
section](#resolving-server-names) above do not affect these - the server
names from before delegation would take place are used. This same
condition applies throughout the request signing process.

Step 2 add `Authorization` header:

    POST /target HTTP/1.1
    Authorization: X-Matrix origin="origin.hs.example.com",destination="destination.hs.example.com",key="ed25519:key1",sig="ABCDEF..."
    Content-Type: application/json

    <JSON-encoded request body>

Example python code:

```py
def authorization_headers(origin_name, origin_signing_key,
                          destination_name, request_method, request_target,
                          content=None):
    request_json = {
         "method": request_method,
         "uri": request_target,
         "origin": origin_name,
         "destination": destination_name,
    }

    if content is not None:
        # Assuming content is already parsed as JSON
        request_json["content"] = content

    signed_json = sign_json(request_json, origin_name, origin_signing_key)

    authorization_headers = []

    for key, sig in signed_json["signatures"][origin_name].items():
        authorization_headers.append(bytes(
            "X-Matrix origin=\"%s\",destination=\"%s\",key=\"%s\",sig=\"%s\"" % (
                origin_name, destination_name, key, sig,
            )
        ))

    return ("Authorization", authorization_headers[0])
```

The format of the `Authorization` header is given in
[Section 11.4 of RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#section-11.4). In
summary, the header begins with authorisation scheme `X-Matrix`, followed by one
or more spaces, followed by a comma-separated list of parameters written as
name=value pairs. Zero or more spaces and tabs around each comma are allowed.
The names are case insensitive and order does not matter. The
values must be enclosed in quotes if they contain characters that are not
allowed in `token`s, as defined in
[Section 5.6.2 of RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2); if a
value is a valid `token`, it may or may not be enclosed in quotes. Quoted
values may include backslash-escaped characters. When parsing the header, the
recipient must unescape the characters. That is, a backslash-character pair is
replaced by the character that follows the backslash.

For compatibility with older servers, the sender should
- only include one space after `X-Matrix`,
- only use lower-case names,
- avoid using backslashes in parameter values, and
- avoid including whitespace around the commas between name=value pairs.

For compatibility with older servers, the recipient should allow colons to be
included in values without requiring the value to be enclosed in quotes.

The authorisation parameters to include are:

- `origin`: the server name of the sending server. This is the same as the
  `origin` field from JSON described in step 1.
- `destination`: {{% added-in v="1.3" %}} the server name of the receiving
  server. This is the same as the `destination` field from the JSON described
  in step 1. For compatibility with older servers, recipients should accept
  requests without this parameter, but MUST always send it. If this property
  is included, but the value does not match the receiving server's name, the
  receiving server must deny the request with an HTTP status code `401
  Unauthorized`.
- `key`: the ID, including the algorithm name, of the sending server's key used
  to sign the request.
- `signature`: the signature of the JSON as calculated in step 1.

Unknown parameters are ignored.

{{% boxes/note %}}
{{% changed-in v="1.11" %}}
This section used to reference [RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235#section-2.1)
and [RFC 7230](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2), that
were obsoleted by RFC 9110 without changes to the sections of interest here.
{{% /boxes/note %}}

### Response Authentication

Responses are authenticated by the TLS server certificate. A homeserver
should not send a request until it has authenticated the connected
server to avoid leaking messages to eavesdroppers.

### Client TLS Certificates

Requests are authenticated at the HTTP layer rather than at the TLS
layer because HTTP services like Matrix are often deployed behind load
balancers that handle the TLS and these load balancers make it difficult
to check TLS client certificates.

A homeserver may provide a TLS client certificate and the receiving
homeserver may check that the client certificate matches the certificate
of the origin homeserver.

## Transactions

The transfer of EDUs and PDUs between homeservers is performed by an
exchange of Transaction messages, which are encoded as JSON objects,
passed over an HTTP PUT request. A Transaction is meaningful only to the
pair of homeservers that exchanged it; they are not globally-meaningful.

Transactions are limited in size; they can have at most 50 PDUs and 100
EDUs.

{{% http-api spec="server-server" api="transactions" %}}

## PDUs

Each PDU contains a single Room Event which the origin server wants to
send to the destination.

The `prev_events` field of a PDU identifies the "parents" of the event,
and thus establishes a partial ordering on events within the room by
linking them into a Directed Acyclic Graph (DAG). The sending server
should populate this field with all of the events in the room for which
it has not yet seen a child - thus demonstrating that the event comes
after all other known events.

For example, consider a room whose events form the DAG shown below. A
server creating a new event in this room should populate the new event's
`prev_events` field with both `E4` and `E6`, since neither event yet has
a child:

    E1
    ^
    |
    E2 <--- E5
    ^       ^
    |       |
    E3      E6
    ^
    |
    E4

For a full schema of what a PDU looks like, see the [room version
specification](/rooms).

### Checks performed on receipt of a PDU

Whenever a server receives an event from a remote server, the receiving
server must ensure that the event:

1.  {{% changed-in v="1.16" %}} Is a valid event, otherwise it is dropped. For
    an event to be valid, it must comply with the event format of that [room version](/rooms).
    For some room versions, a `room_id` may also be required on the event in order
    to determine the room version to check the event against. See the event format
    section of the [room version specifications](/rooms) for details on when it
    is required.
2.  Passes signature checks, otherwise it is dropped.
3.  Passes hash checks, otherwise it is redacted before being processed
    further.
4.  Passes authorisation rules based on the event's auth events,
    otherwise it is rejected.
5.  Passes authorisation rules based on the state before the event,
    otherwise it is rejected.
6.  Passes authorisation rules based on the current state of the room,
    otherwise it is "soft failed".
7. {{% added-in v="1.18" %}} Is [validated](#validating-policy-server-signatures)
   by the Policy Server, if the room is [using a Policy Server](#determining-if-a-policy-server-is-enabled-in-a-room),
   otherwise it is "soft failed".

Further details of these checks, and how to handle failures, are
described below.

The [Signing Events](#signing-events) section has more information on
which hashes and signatures are expected on events, and how to calculate
them.

#### Definitions

Required Power Level

: A given event type has an associated *required power level*. This is given by
  the current [`m.room.power_levels`](/client-server-api/#mroompower_levels)
  event. The event type is either listed explicitly in the `events` section or
  given by either `state_default` or `events_default` depending on if the event
  is a state event or not.

Invite Level, Kick Level, Ban Level, Redact Level

: The levels given by the `invite`, `kick`, `ban`, and `redact` properties in
  the current [`m.room.power_levels`](/client-server-api/#mroompower_levels)
  state. The invite level defaults to 0 if unspecified. The kick level, ban level
  and redact level each default to 50 if unspecified.

Target User

: For an [`m.room.member`](/client-server-api/#mroommember) state event, the
  user given by the `state_key` of the event.

{{% boxes/warning %}}
Some [room versions](/rooms) accept power level values to be represented as
strings rather than integers. This is strictly for backwards compatibility.
A homeserver should take reasonable precautions to prevent users from sending
new power level events with string values (eg: by rejecting the API request),
and must never populate the default power levels in a room as string values.

See the [room version specification](/rooms) for more information.
{{% /boxes/warning %}}

#### Authorisation rules

The rules governing whether an event is authorised depends on a set of
state. A given event is checked multiple times against different sets of
state, as specified above. Each room version can have a different
algorithm for how the rules work, and which rules are applied. For more
detailed information, please see the [room version
specification](/rooms).

##### Auth events selection

The `auth_events` field of a PDU identifies the set of events which give
the sender permission to send the event. The `auth_events` for the
`m.room.create` event in a room is empty; for other events, it should be
the following subset of the room state:

- {{% changed-in v="1.16" %}} Depending on the [room version](/rooms), the
  `m.room.create` event.

- The current `m.room.power_levels` event, if any.

- The sender's current `m.room.member` event, if any.

- If type is `m.room.member`:

    - The target's current `m.room.member` event, if any.
    - If `membership` is `join`, `invite` or `knock`, the current
      `m.room.join_rules` event, if any.
    - If membership is `invite` and `content` contains a
      `third_party_invite` property, the current
      `m.room.third_party_invite` event with `state_key` matching
      `content.third_party_invite.signed.token`, if any.
    - If `membership` is `join`, `content.join_authorised_via_users_server`
      is present, and the [room version supports restricted rooms](/rooms/#feature-matrix),
      then the `m.room.member` event with `state_key` matching
      `content.join_authorised_via_users_server`.

#### Rejection

If an event is rejected it should neither be relayed to clients nor be
included as a prev event in any new events generated by the server.
Subsequent events from other servers that reference rejected events
should be allowed if they still pass the auth rules. The state used in
the checks should be calculated as normal, except not updating with the
rejected event where it is a state event.

If an event in an incoming transaction is rejected, this should not
cause the transaction request to be responded to with an error response.

{{% boxes/note %}}
This means that events may be included in the room DAG even though they
should be rejected.
{{% /boxes/note %}}

{{% boxes/note %}}
This is in contrast to redacted events which can still affect the state
of the room. For example, a redacted `join` event will still result in
the user being considered joined.
{{% /boxes/note %}}

#### Soft failure

{{% boxes/rationale %}}
It is important that we prevent users from evading bans (or other power
restrictions) by creating events which reference old parts of the DAG.
For example, a banned user could continue to send messages to a room by
having their server send events which reference the event before they
were banned. Note that such events are entirely valid, and we cannot
simply reject them, as it is impossible to distinguish such an event
from a legitimate one which has been delayed. We must therefore accept
such events and let them participate in state resolution and the
federation protocol as normal. However, servers may choose not to send
such events on to their clients, so that end users won't actually see
the events.

When this happens it is often fairly obvious to servers, as they can see
that the new event doesn't actually pass auth based on the "current
state" (i.e. the resolved state across all forward extremities). While
the event is technically valid, the server can choose to not notify
clients about the new event.

This discourages servers from sending events that evade bans etc. in
this way, as end users won't actually see the events.
{{% /boxes/rationale %}}

When the homeserver receives a new event over federation it should also
check whether the event passes auth checks based on the current state of
the room (as well as based on the state at the event). If the event does
not pass the auth checks based on the *current state* of the room (but
does pass the auth checks based on the state at that event) it should be
"soft failed".

When an event is "soft failed" it should not be relayed to the client
nor be referenced by new events created by the homeserver (i.e. they
should not be added to the server's list of forward extremities of the
room). Soft failed events are otherwise handled as usual.

{{% boxes/note %}}
Soft failed events participate in state resolution as normal if further
events are received which reference it. It is the job of the state
resolution algorithm to ensure that malicious events cannot be injected
into the room state via this mechanism.
{{% /boxes/note %}}

{{% boxes/note %}}
Because soft failed state events participate in state resolution as
normal, it is possible for such events to appear in the current state of
the room. In that case the client should be told about the soft failed
event in the usual way (e.g. by sending it down in the `state` section
of a sync response).
{{% /boxes/note %}}

{{% boxes/note %}}
A soft failed event should be returned in response to federation
requests where appropriate (e.g. in `/event/<event_id>`). Note that soft
failed events are returned in `/backfill` and `/get_missing_events`
responses only if the requests include events referencing the soft
failed events.
{{% /boxes/note %}}

Example

As an example consider the event graph:

      A
     /
    B

where `B` is a ban of a user `X`. If the user `X` tries to set the topic
by sending an event `C` while evading the ban:

      A
     / \
    B   C

servers that receive `C` after `B` should soft fail event `C`, and so
will neither relay `C` to its clients nor send any events referencing
`C`.

If later another server sends an event `D` that references both `B` and
`C` (this can happen if it received `C` before `B`):

      A
     / \
    B   C
     \ /
      D

then servers will handle `D` as normal. `D` is sent to the servers'
clients (assuming `D` passes auth checks). The state at `D` may resolve
to a state that includes `C`, in which case clients should also to be
told that the state has changed to include `C`. (*Note*: This depends on
the exact state resolution algorithm used. In the original version of
the algorithm `C` would be in the resolved state, whereas in latter
versions the algorithm tries to prioritise the ban over the topic
change.)

Note that this is essentially equivalent to the situation where one
server doesn't receive `C` at all, and so asks another server for the
state of the `C` branch.

Let's go back to the graph before `D` was sent:

      A
     / \
    B   C

If all the servers in the room saw `B` before `C` and so soft fail `C`,
then any new event `D'` will not reference `C`:

      A
     / \
    B   C
    |
    D'

{{% boxes/note %}}
{{% added-in v="1.18" %}}
Events can also be soft failed if they fail [Policy Server checks](#validating-policy-server-signatures).
{{% /boxes/note %}}

#### Retrieving event authorisation information

The homeserver may be missing event authorisation information, or wish
to check with other servers to ensure it is receiving the correct auth
chain. These APIs give the homeserver an avenue for getting the
information it needs.

{{% http-api spec="server-server" api="event_auth" %}}

## EDUs

EDUs, by comparison to PDUs, do not have an ID, a room ID, or a list of
"previous" IDs. They are intended to be non-persistent data such as user
presence, typing notifications, etc.

{{% definition path="api/server-server/definitions/edu_with_example" %}}

## Room State Resolution

The *state* of a room is a map of `(event_type, state_key)` to
`event_id`. Each room starts with an empty state, and each state event
which is accepted into the room updates the state of that room.

Where each event has a single `prev_event`, it is clear what the state
of the room after each event should be. However, when two branches in
the event graph merge, the state of those branches might differ, so a
*state resolution* algorithm must be used to determine the resultant
state.

For example, consider the following event graph (where the oldest event,
E0, is at the top):

      E0
      |
      E1
     /  \
    E2  E4
    |    |
    E3   |
     \  /
      E5

Suppose E3 and E4 are both `m.room.name` events which set the name of
the room. What should the name of the room be at E5?

The algorithm to be used for state resolution depends on the room
version. For a description of each room version's algorithm, please see
the [room version specification](/rooms).

## Backfilling and retrieving missing events

Once a homeserver has joined a room, it receives all the events emitted
by other homeservers in that room, and is thus aware of the entire
history of the room from that moment onwards. Since users in that room
are able to request the history by the `/messages` client API endpoint,
it's possible that they might step backwards far enough into history
before the homeserver itself was a member of that room.

To cover this case, the federation API provides a server-to-server
analog of the `/messages` client API, allowing one homeserver to fetch
history from another. This is the `/backfill` API.

To request more history, the requesting homeserver picks another
homeserver that it thinks may have more (most likely this should be a
homeserver for some of the existing users in the room at the earliest
point in history it has currently), and makes a `/backfill` request.

Similar to backfilling a room's history, a server may not have all the
events in the graph. That server may use the `/get_missing_events` API
to acquire the events it is missing.

{{% http-api spec="server-server" api="backfill" %}}

## Retrieving events

In some circumstances, a homeserver may be missing a particular event or
information about the room which cannot be easily determined from
backfilling. These APIs provide homeservers with the option of getting
events and the state of the room at a given point in the timeline.

{{% http-api spec="server-server" api="events" %}}

## Joining Rooms

When a new user wishes to join a room that the user's homeserver already
knows about, the homeserver can immediately determine if this is
allowable by inspecting the state of the room. If it is acceptable, it
can generate, sign, and emit a new `m.room.member` state event adding
the user into that room. When the homeserver does not yet know about the
room it cannot do this directly. Instead, it must take a longer
multi-stage handshaking process by which it first selects a remote
homeserver which is already participating in that room, and use it to
assist in the joining process. This is the remote join handshake.

This handshake involves the homeserver of the new member wishing to join
(referred to here as the "joining" server), the directory server hosting
the room alias the user is requesting to join with, and a homeserver
where existing room members are already present (referred to as the
"resident" server).

In summary, the remote join handshake consists of the joining server
querying the directory server for information about the room alias;
receiving a room ID and a list of join candidates. The joining server
then requests information about the room from one of the residents. It
uses this information to construct an `m.room.member` event which it
finally sends to a resident server.

Conceptually these are three different roles of homeserver. In practice
the directory server is likely to be resident in the room, and so may be
selected by the joining server to be the assisting resident. Likewise,
it is likely that the joining server picks the same candidate resident
for both phases of event construction, though in principle any valid
candidate may be used at each time. Thus, any join handshake can
potentially involve anywhere from two to four homeservers, though most
in practice will use just two.

<!--
https://textart.io/sequence

object Client JoiningServer DirectoryServer ResidentServer
Client->JoiningServer: join request
JoiningServer->DirectoryServer: directory request
DirectoryServer->JoiningServer: directory response
JoiningServer->ResidentServer: make_join request
ResidentServer->JoiningServer: make_join response
JoiningServer->ResidentServer: send_join request
ResidentServer->JoiningServer: send_join response
JoiningServer->Client: join response
-->

```nohighlight
+---------+          +---------------+            +-----------------+ +-----------------+
| Client  |          | JoiningServer |            | DirectoryServer | | ResidentServer  |
+---------+          +---------------+            +-----------------+ +-----------------+
     |                       |                             |                   |
     | join request          |                             |                   |
     |---------------------->|                             |                   |
     |                       |                             |                   |
     |                       | directory request           |                   |
     |                       |---------------------------->|                   |
     |                       |                             |                   |
     |                       |          directory response |                   |
     |                       |<----------------------------|                   |
     |                       |                             |                   |
     |                       | make_join request           |                   |
     |                       |------------------------------------------------>|
     |                       |                             |                   |
     |                       |                             |make_join response |
     |                       |<------------------------------------------------|
     |                       |                             |                   |
     |                       | send_join request           |                   |
     |                       |------------------------------------------------>|
     |                       |                             |                   |
     |                       |                             |send_join response |
     |                       |<------------------------------------------------|
     |                       |                             |                   |
     |         join response |                             |                   |
     |<----------------------|                             |                   |
     |                       |                             |                   |
```

The first part of the handshake usually involves using the directory
server to request the room ID and join candidates through the
[`/query/directory`](/server-server-api/#get_matrixfederationv1querydirectory) API endpoint. In the case of a new user joining a
room as a result of a received invite, the joining user's homeserver
could optimise this step away by picking the origin server of that
invite message as the join candidate. However, the joining server should
be aware that the origin server of the invite might since have left the
room, so should be prepared to fall back on the regular join flow if
this optimisation fails.

Once the joining server has the room ID and the join candidates, it then
needs to obtain enough information about the room to fill in the
required fields of the `m.room.member` event. It obtains this by
selecting a resident from the candidate list, and using the
`GET /make_join` endpoint. The resident server will then reply with
enough information for the joining server to fill in the event.

The joining server is expected to add or replace the `origin`,
`origin_server_ts`, and `event_id` on the templated event received by the
resident server. The joining server MUST also verify that the `type`, `room_id`,
`sender`, `state_key` and `content.membership` fields have the expected values.
This event is then signed by the joining server.

To complete the join handshake, the joining server submits this new event
to the resident server it used for `GET /make_join`, using the `PUT /send_join`
endpoint.

The resident homeserver then adds its signature to this event and
accepts it into the room's event graph. The joining server receives
the full set of state for the newly-joined room as well as the freshly
signed membership event. The resident server must also send the event
to other servers participating in the room.

{{% http-api spec="server-server" api="joins-v1" %}}

{{% http-api spec="server-server" api="joins-v2" %}}

### Restricted rooms

Restricted rooms are described in detail in the
[client-server API](/client-server-api/#restricted-rooms) and are available
in room versions [which support restricted join rules](/rooms/#feature-matrix).

A resident server processing a request to join a restricted room must
ensure that the joining server satisfies at least one of the conditions
specified by `m.room.join_rules`. If no conditions are available, or none
match the required schema, then the joining server is considered to have
failed all conditions.

The resident server uses a 400 `M_UNABLE_TO_AUTHORISE_JOIN` error on
`/make_join` and `/send_join` to denote that the resident server is unable
to validate any of the conditions, usually because the resident server
does not have state information about rooms required by the conditions.

The resident server uses a 400 `M_UNABLE_TO_GRANT_JOIN` error on `/make_join`
and `/send_join` to denote that the joining server should try a different
server. This is typically because the resident server can see that the
joining user satisfies one of the conditions, though the resident server
would be unable to meet the auth rules governing `join_authorised_via_users_server`
on the resulting `m.room.member` event.

If the joining server fails all conditions then a 403 `M_FORBIDDEN` error
is used by the resident server.

## Knocking upon a room {#knocking-rooms}

Rooms can permit knocking through the join rules, and if permitted this
gives users a way to request to join (be invited) to the room. Users who
knock on a room where the server is already a resident of the room can
just send the knock event directly without using this process, however
much like [joining rooms](/server-server-api/#joining-rooms) the server
must handshake their way into having the knock sent on its behalf.

The handshake is largely the same as the joining rooms handshake, where
instead of a "joining server" there is a "knocking server", and the APIs
to be called are different (`/make_knock` and `/send_knock`).

Servers can retract knocks over federation by leaving the room, as described
below for rejecting invites.

{{% http-api spec="server-server" api="knocks" %}}

## Inviting to a room

When a user on a given homeserver invites another user on the same
homeserver, the homeserver may sign the membership event itself and skip
the process defined here. However, when a user invites another user on a
different homeserver, a request to that homeserver to have the event
signed and verified must be made.

Note that invites are used to indicate that knocks were accepted. As such,
receiving servers should be prepared to manually link up a previous knock
to an invite if the invite event does not directly reference the knock.

{{% boxes/note %}}
{{% added-in v="1.16" %}} `invite_room_state` MUST now have its entries formatted
according to the room's version (see [room version specification](/rooms)). However,
servers SHOULD consider their local ecosystems before returning the described
`400 M_MISSING_PARAM` error code. While migrating, servers SHOULD warn about
invites which fail the validation rather than error in room versions 1 through 11.
All invites to other room versions which fail validation SHOULD result in an error.

The specification suggests that servers finish their migration no later than
January 2026, though servers may extend this as required to support their users.
{{% /boxes/note %}}

{{% http-api spec="server-server" api="invites-v1" %}}

{{% http-api spec="server-server" api="invites-v2" %}}

## Leaving Rooms (Rejecting Invites)

Normally homeservers can send appropriate `m.room.member` events to have
users leave the room, to reject local invites, or to retract a knock.
Remote invites/knocks from other homeservers do not involve the server in the
graph and therefore need another approach to reject the invite. Joining
the room and promptly leaving is not recommended as clients and servers will
interpret that as accepting the invite, then leaving the room rather
than rejecting the invite.

Similar to the [Joining Rooms](#joining-rooms) handshake, the server
which wishes to leave the room starts with sending a `/make_leave`
request to a resident server. In the case of rejecting invites, the
resident server may be the server which sent the invite. After receiving
a template event from `/make_leave`, the leaving server signs the event
and replaces the `event_id` with its own. This is then sent to the
resident server via `/send_leave`. The resident server will then send
the event to other servers in the room.

{{% http-api spec="server-server" api="leaving-v1" %}}

{{% http-api spec="server-server" api="leaving-v2" %}}

## Third-party invites

{{% boxes/note %}}
More information about third-party invites is available in the Client-Server API
under the [Third-party invites](/client-server-api/#third-party-invites) module.
{{% /boxes/note %}}

When a user wants to invite another user in a room but doesn't know the
Matrix ID to invite, they can do so using a third-party identifier (e.g.
an e-mail or a phone number).

This identifier and its bindings to Matrix IDs are verified by an
identity server implementing the [Identity Service
API](/identity-service-api).

### Cases where an association exists for a third-party identifier

If the third-party identifier is already bound to a Matrix ID, a [lookup
request](/identity-service-api/#post_matrixidentityv2lookup) on the identity
server will return it. The invite is then processed by the inviting homeserver
as a [standard `m.room.member` invite event](#inviting-to-a-room). This is the
simplest case.

### Cases where an association doesn't exist for a third-party identifier

If the third-party identifier isn't bound to any Matrix ID, the inviting
homeserver will request the identity server to [store an invite](/identity-service-api/#invitation-storage)
for this identifier and to deliver it to whoever binds it to its Matrix ID. It
will also send an [`m.room.third_party_invite`](/client-server-api/#mroomthird_party_invite)
event in the room to specify a display name, a token and public keys the
identity server provided as a response to the invite storage request.

When a third-party identifier with pending invites gets bound to a Matrix ID,
the identity server will send a request to the [`/3pid/onbind`](#put_matrixfederationv13pidonbind)
endpoint of the the ID's homeserver as described in the [Invitation
Storage](/identity-service-api#invitation-storage) section of the Identity
Service API.

The following process applies for each invite sent by the identity
server:

The invited homeserver will create an [`m.room.member`](/client-server-api/#mroommember)
invite event containing a special `third_party_invite` section containing the
token and a `signed` object, both provided by the identity server.

If the invited homeserver is in the room the invite came from, it can
auth the event and send it.

However, if the invited homeserver isn't in the room the invite came
from, it will need to request the inviting homeserver to auth the event
at the [`/exchange_third_party_invite`](#put_matrixfederationv1exchange_third_party_inviteroomid)
endpoint.

{{% http-api spec="server-server" api="third_party_invite" %}}

#### Verifying the invite

When a homeserver receives an `m.room.member` invite event for a room
it's in with a `third_party_invite` object, it must verify that the
association between the third-party identifier initially invited to the
room and the Matrix ID that claims to be bound to it has been verified
without having to rely on a third-party server.

To do so, it will fetch from the room's state events the
`m.room.third_party_invite` event for which the state key matches with
the value for the `token` key in the `third_party_invite` object from
the `m.room.member` event's content to fetch the public keys initially
delivered by the identity server that stored the invite.

It will then use these keys to verify that the `signed` object (in the
`third_party_invite` object from the `m.room.member` event's content)
was signed by the same identity server.

Since this `signed` object can only be delivered once in the POST
request emitted by the identity server upon binding between the
third-party identifier and the Matrix ID, and contains the invited
user's Matrix ID and the token delivered when the invite was stored,
this verification will prove that the `m.room.member` invite event comes
from the user owning the invited third-party identifier.

## Published Room Directory

To complement the [room directory in the Client-Server API](/client-server-api#published-room-directory),
homeservers need a way to query the published rooms of another server.
This can be done by making a request to the `/publicRooms` endpoint for
the server the room directory should be retrieved for.

{{% http-api spec="server-server" api="public_rooms" %}}

## Spaces

To complement the [Client-Server API's Spaces module](/client-server-api/#spaces),
homeservers need a way to query information about spaces from other servers.

{{% http-api spec="server-server" api="space_hierarchy" %}}

## Typing Notifications

When a server's users send typing notifications, those notifications
need to be sent to other servers in the room so their users are aware of
the same state. Receiving servers should verify that the user is in the
room, and is a user belonging to the sending server.

{{% definition path="api/server-server/definitions/event-schemas/m.typing" %}}

## Presence

The server API for presence is based entirely on exchange of the
following EDUs. There are no PDUs or Federation Queries involved.

Servers should only send presence updates for users that the receiving
server would be interested in. Such as the receiving server sharing a
room with a given user.

{{% definition path="api/server-server/definitions/event-schemas/m.presence" %}}

## Receipts

Receipts are EDUs used to communicate a marker for a given event.
Currently the only kind of receipt supported is a "read receipt", or
where in the event graph the user has read up to.

Read receipts for events that a user sent do not need to be sent. It is
implied that by sending the event the user has read up to the event.

{{% definition path="api/server-server/definitions/event-schemas/m.receipt" %}}

## Querying for information

Queries are a way to retrieve information from a homeserver about a
resource, such as a user or room. The endpoints here are often called in
conjunction with a request from a client on the client-server API in
order to complete the call.

There are several types of queries that can be made. The generic
endpoint to represent all queries is described first, followed by the
more specific queries that can be made.

{{% http-api spec="server-server" api="query" %}}

## OpenID

Third-party services can exchange an access token previously generated
by the <span class="title-ref">Client-Server API</span> for information
about a user. This can help verify that a user is who they say they are
without granting full access to the user's account.

Access tokens generated by the OpenID API are only good for the OpenID
API and nothing else.

{{% http-api spec="server-server" api="openid" %}}

## Device Management

Details of a user's devices must be efficiently published to other users
and kept up-to-date. This is critical for reliable end-to-end
encryption, in order for users to know which devices are participating
in a room. It's also required for to-device messaging to work. This
section is intended to complement the [Device Management
module](/client-server-api#device-management)
of the Client-Server API.

Matrix currently uses a custom pubsub system for synchronising
information about the list of devices for a given user over federation.
When a server wishes to determine a remote user's device list for the
first time, it should populate a local cache from the result of a
`/user/keys/query` API on the remote server. However, subsequent updates
to the cache should be applied by consuming `m.device_list_update` EDUs.
Each new `m.device_list_update` EDU describes an incremental change to
one device for a given user which should replace any existing entry in
the local server's cache of that device list. Servers must send
`m.device_list_update` EDUs to all the servers who share a room with a
given local user, and must be sent whenever that user's device list
changes (i.e. for new or deleted devices, when that user joins a room
which contains servers which are not already receiving updates for that
user's device list, or changes in device information such as the
device's human-readable name).

Servers send `m.device_list_update` EDUs in a sequence per origin user,
each with a unique `stream_id`. They also include a pointer to the most
recent previous EDU(s) that this update is relative to in the `prev_id`
field. To simplify implementation for clustered servers which could send
multiple EDUs at the same time, the `prev_id` field should include all
`m.device_list_update` EDUs which have not been yet been referenced in a
EDU. If EDUs are emitted in series by a server, there should only ever
be one `prev_id` in the EDU.

This forms a simple directed acyclic graph of `m.device_list_update`
EDUs, showing which EDUs a server needs to have received in order to
apply an update to its local copy of the remote user's device list. If a
server receives an EDU which refers to a `prev_id` it does not
recognise, it must resynchronise its list by calling the
`/user/keys/query API` and resume the process. The response contains a
`stream_id` which should be used to correlate with subsequent
`m.device_list_update` EDUs.

{{% http-api spec="server-server" api="user_devices" %}}

{{% definition path="api/server-server/definitions/event-schemas/m.device_list_update" %}}

## End-to-End Encryption

This section complements the [End-to-End Encryption
module](/client-server-api#end-to-end-encryption)
of the Client-Server API. For detailed information about end-to-end
encryption, please see that module.

The APIs defined here are designed to be able to proxy much of the
client's request through to federation, and have the response also be
proxied through to the client.

{{% http-api spec="server-server" api="user_keys" %}}

{{% definition path="api/server-server/definitions/event-schemas/m.signing_key_update" %}}

## Send-to-device messaging

The server API for send-to-device messaging is based on the
`m.direct_to_device` EDU. There are no PDUs or Federation Queries
involved.

Each send-to-device message should be sent to the destination server
using the following EDU:

{{% definition path="api/server-server/definitions/event-schemas/m.direct_to_device" %}}

## Content Repository

Attachments to events (images, files, etc) are uploaded to a homeserver
via the Content Repository described in the [Client-Server
API](/client-server-api/#content-repository). When a server wishes
to serve content originating from a remote server, it needs to ask the
remote server for the media.

Servers MUST use the server described in the [Matrix Content URI](/client-server-api/#matrix-content-mxc-uris).
Formatted as `mxc://{ServerName}/{MediaID}`, servers MUST download the media from
`ServerName` using the below endpoints.

{{% changed-in v="1.11" %}} Servers were previously advised to use the `/_matrix/media/*`
endpoints described by the [Content Repository module in the Client-Server API](/client-server-api/#content-repository),
however, those endpoints have been deprecated. New endpoints are introduced which
require authentication. Naturally, as a server is not a user, they cannot provide
the required access token to those endpoints. Instead, servers MUST try the endpoints
described below before falling back to the deprecated `/_matrix/media/*` endpoints
when they receive a `404 M_UNRECOGNIZED` error. When falling back, servers MUST
be sure to set `allow_remote` to `false`.

{{% http-api spec="server-server" api="content_repository" %}}

## Server Access Control Lists (ACLs)

Server ACLs and their purpose are described in the [Server
ACLs](/client-server-api#server-access-control-lists-acls-for-rooms)
section of the Client-Server API.

When a remote server makes a request, it MUST be verified to be allowed
by the server ACLs. If the server is denied access to a room, the
receiving server MUST reply with a 403 HTTP status code and an `errcode`
of `M_FORBIDDEN`.

The following endpoint prefixes MUST be protected:

-   `/_matrix/federation/v1/make_join`
-   `/_matrix/federation/v1/make_leave`
-   `/_matrix/federation/v1/send_join`
-   `/_matrix/federation/v2/send_join`
-   `/_matrix/federation/v1/send_leave`
-   `/_matrix/federation/v2/send_leave`
-   `/_matrix/federation/v1/invite`
-   `/_matrix/federation/v2/invite`
-   `/_matrix/federation/v1/make_knock`
-   `/_matrix/federation/v1/send_knock`
-   `/_matrix/federation/v1/state`
-   `/_matrix/federation/v1/state_ids`
-   `/_matrix/federation/v1/backfill`
-   `/_matrix/federation/v1/event_auth`
-   `/_matrix/federation/v1/get_missing_events`

Additionally the [`/_matrix/federation/v1/send/{txnId}`](#put_matrixfederationv1sendtxnid)
endpoint MUST be protected as follows:

-   ACLs MUST be applied to all PDUs on a per-PDU basis. If the sending
    server is denied access to the room identified by `room_id`, the PDU
    MUST be ignored with an appropriate error included in the response
    for the respective event ID.
-   ACLs MUST be applied to all EDUs that are local to a specific room:

    -   For [typing notifications (`m.typing`)](#typing-notifications), if
        the sending server is denied access to the room identified by
        `room_id`, the EDU MUST be ignored.
    -   For [receipts (`m.receipt`)](#receipts), all receipts for a particular
        room ID MUST be ignored if the sending server is denied access to
        the room identified by that ID.

The following endpoints MAY be protected:

-   [`/_matrix/policy/v1/sign`](#post_matrixpolicyv1sign) - {{< added-in v="1.18" >}}
    Protected if the server is tracking the DAG and chooses to enforce the ACL.


## Policy Servers

{{% added-in v="1.18" %}}

Policy Servers are an available tool for rooms to add proactive protections. Rooms
which use a Policy Server (PS) can prevent unwanted events from reaching users. Rooms
are not required to use a Policy Server, and can disable it any time after enabling
it.

For a homeserver to be a Policy Server, it MUST implement the following functionality
of the Server-Server API:

* [Normal server name resolution](#resolving-server-names).
* [Publishing a signing key](#publishing-keys).
* [Request authentication](#authentication).
* Being able to [make and send join requests](#joining-rooms).
* Receiving and processing [`POST /_matrix/policy/v1/sign`](#post_matrixpolicyv1sign)
  requests.

All other functionality and endpoints are optional for a Policy Server.

{{% boxes/note %}}
Though a Policy Server is not required to implement the full Server-Server API
surface, some functionality may be desirable to implement anyway:

* Receiving [invites](#inviting-to-a-room) can make it easier to add the Policy
  Server to a room.
* Receiving [transactions](#transactions) is recommended to avoid remote servers
  flagging the Policy Server as "offline", even if the contents are discarded.
* Receiving [device lookups](#get_matrixfederationv1userdevicesuserid) can also
  help reduce remote servers flagging the Policy Server as "offline".
{{% /boxes/note %}}

{{% boxes/note %}}
Policy Servers are *not* required to track the DAG for a room. Policy Servers
might be optimized for content moderation and therefore do not need knowledge of
the DAG.
{{% /boxes/note %}}

### Determining if a Policy Server is enabled in a room

For a room to be considered as "using" a Policy Server, *all* of the following
conditions MUST be true:

* The *current state* for the room has a valid [`m.room.policy`](/client-server-api/#mroompolicy)
  state event with empty state key. Valid means `content` contains at least:
  * A string value for `via`.
  * An object value for `public_keys` containing at least a string value for
    `ed25519`.
* The server name denoted by the `m.room.policy` state event's `via` has at least
  one joined user in the room in *current state*. The user does not need any
  special permissions or power levels in the room.

If a room has enabled a Policy Server, *all* servers in that room MUST [ask that
Policy Server](#asking-for-a-policy-server-signature-on-an-event) for a signature
before sending an event. If the Policy Server refuses to sign the event being
sent, servers SHOULD fail to send that event as per the [validation rules](#validating-policy-server-signatures).

{{% boxes/note %}}
"Current state" shares the same definition as [soft failure](#soft-failure).
{{% /boxes/note %}}

{{% boxes/note %}}
Policy Servers MUST have at least one joined user in the room to give the Policy
Server agency in whether it serves that role for the room. Otherwise, a room
would be able to force a Policy Server to participate and then overwhelm it.
{{% /boxes/note %}}

### Validating Policy Server signatures

Policy Servers use signatures to indicate whether an event was checked and is
recommended for inclusion in a room. The Policy Server's recommendation does *not*
affect the authorization rules for an event, but does affect whether homeservers
[soft fail](#soft-failure) or refuse to actually go forward with sending an event.

If a room has disabled (or never enabled) a Policy Server, events are recommended
for inclusion and subject to normal authorization rules.

{{% boxes/note %}}
Because the Policy Server is not asked to sign [`m.room.policy`](/client-server-api/#mroompolicy)
state events with empty string `state_key`s, those events will not have a Policy
Server signature. Those events are by default recommended for inclusion and still
subject to normal authorization rules.

All other events SHOULD have a valid Policy Server signature when a Policy Server
is enabled in the room, including non-state `m.room.policy` events and `m.room.policy`
state events with non-empty `state_key`s. This also includes state events like
membership and power level changes.
{{% /boxes/note %}}

If a room has enabled a Policy Server, the Policy Server's signature appears
alongside the normal [event signatures](#signing-events), though uses a public
key from the room's `m.room.policy` state event.

{{% boxes/note %}}
Currently, only Ed25519 keys are supported by homeservers. The Key ID for the
`ed25519` key in `m.room.policy` is *always* `ed25519:policy_server`.
{{% /boxes/note %}}

{{% boxes/note %}}
By embedding the Policy Server's public key into room state, the Policy Server
does not need to be online or have its keys cached by a notary in order to validate
an event.
{{% /boxes/note %}}

{{% boxes/warning %}}
The Policy Server's [published signing key](#publishing-keys) SHOULD NOT be the
same key contained in the `m.room.policy` state event. This is to keep "can send
federation traffic" and "can recommend events for inclusion" separate, and to
allow rooms to revoke the Policy Server's key without cooperation of the Policy
Server.

If the Policy Server is acting as a normal homeserver and attempting to send an
event, that event will require a signature from the server's published signing
key alongside the Policy Server signature described in this section.
{{% /boxes/warning %}}

If the Policy Server's signature is valid, the event is recommended for inclusion
in the room, and is still subject to normal authorization rules.

If the Policy Server's signature is invalid or missing, the homeserver SHOULD
[ask for a new signature](#asking-for-a-policy-server-signature-on-an-event). If
the event still does not have a valid Policy Server signature, the event is *not*
recommended for inclusion and the homeserver SHOULD:

* If applicable, reject the Client-Server API request which was generating the
  event. If the Policy Server returned an error, that error SHOULD be provided
  to the client verbatim. The event the client was attempting to send SHOULD be
  discarded and not processed/sent any further.
* If the event was received over federation or the homeserver sends a local client's
  event anyway, [soft fail](#soft-failure) the event.

{{% boxes/note %}}
For clarity, an event which lacks a valid Policy Server signature is still subject
to normal authorization rules.
{{% /boxes/note %}}

{{% boxes/note %}}
When a Policy Server's signature is invalid, a new one is requested because the
Policy Server's key may have rotated between the event being signed and the event
being checked.

Because the key is contained in the `m.room.policy` state event, if the Policy
Server rotates its key without the room also updating the state event, events
will be flagged as not recommended for inclusion. This is expected behaviour.
{{% /boxes/note %}}

### Asking for a Policy Server signature on an event

Per [above](#determining-if-a-policy-server-is-enabled-in-a-room), if a room has
a Policy Server enabled, *all* servers in the room MUST ask the Policy Server to
sign their events before sending them to clients and servers.

After asking for a signature, homeservers SHOULD [validate](#validating-policy-server-signatures)
the returned signature before continuing to send events. If the Policy Server
returns an error, the event SHOULD NOT be sent to clients and servers.

{{% http-api spec="server-server" api="room_policy" %}}

## Signing Events

Signing events is complicated by the fact that servers can choose to
redact non-essential parts of an event.

### Adding hashes and signatures to outgoing events

Before signing the event, the *content hash* of the event is calculated
as described below. The hash is encoded using [Unpadded
Base64](/appendices#unpadded-base64) and stored in the event
object, in a `hashes` object, under a `sha256` key.

The event object is then *redacted*, following the [redaction
algorithm](/client-server-api#redactions).
Finally it is signed as described in [Signing
JSON](/appendices#signing-json), using the server's signing key
(see also [Retrieving server keys](#retrieving-server-keys)).

The signature is then copied back to the original event object.

For an example of a signed event, see the [room version
specification](/rooms).

{{% boxes/note %}}
{{% added-in v="1.18" %}}
Events sent in rooms with [Policy Servers](#policy-servers) MUST [ask](#asking-for-a-policy-server-signature-on-an-event)
the Policy Server for a signature too.
{{% /boxes/note %}}

### Validating hashes and signatures on received events

When a server receives an event over federation from another server, the
receiving server should check the hashes and signatures on that event.

First the signatures are checked. The event is redacted following the
[redaction algorithm](/client-server-api#redactions), and
the resultant object is checked for signatures from the originating
server, following the algorithm described in [Checking for a
signature](/appendices#checking-for-a-signature). Note that this
step should succeed whether we have been sent the full event or a
redacted copy.

For room versions 3 and later, unless the event is a 3rd party invite, only the
signature(s) from the originating server (the server the `sender` belongs to)
are required for verification. Room versions 1 and 2 also require that a
signature is present from the domain in the `event_id`, if it differs from the
originating server. If a signature is from an unknown or expired key, it is
skipped.

If the event is a 3rd party invite, the sender must already match the 3rd party
invite, and the server which actually sends the event may be a different
server.

Only signatures from known server keys are validated here. Any unknown keys are ignored.
In particular, the [policy server key](#validating-policy-server-signatures) is not
expected to be published and therefore should be skipped at this stage.
Additionally, any keys that are known to have expired prior to the event's
`origin_server_ts` are ignored.

{{% boxes/note %}}
{{% added-in v="1.18" %}}
Events sent in rooms with [Policy Servers](#policy-servers) have [additional](#validating-policy-server-signatures)
signature validation requirements.
{{% /boxes/note %}}

If all signatures from known unexpired keys from the originating server(s) are
found to be valid, the expected content hash is calculated as described below.
The content hash in the `hashes` property of the received event is base64-decoded,
and the two are compared for equality.

If the hash check fails, then it is assumed that this is because we have
only been given a redacted version of the event. To enforce this, the
receiving server should use the redacted copy it calculated rather than
the full copy it received.

### Calculating the reference hash for an event

The *reference hash* of an event covers the essential fields of an
event, including content hashes. It is used for event identifiers in
some room versions. See the [room version
specification](/rooms) for more information. It is
calculated as follows.

1.  The event is put through the redaction algorithm.
2.  The `signatures` and `unsigned` properties are removed
    from the event, if present.
3.  The event is converted into [Canonical
    JSON](/appendices#canonical-json).
4.  A sha256 hash is calculated on the resulting JSON object.

### Calculating the content hash for an event

The *content hash* of an event covers the complete event including the
*unredacted* contents. It is calculated as follows.

First, any existing `unsigned`, `signatures`, and `hashes` properties are
removed. The resulting object is then encoded as [Canonical
JSON](/appendices#canonical-json), and the JSON is hashed using
SHA-256.

### Example code

```py
def hash_and_sign_event(event_object, signing_key, signing_name):
    # First we need to hash the event object.
    content_hash = compute_content_hash(event_object)
    event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)}

    # Strip all the keys that would be removed if the event was redacted.
    # The hashes are not stripped and cover all the keys in the event.
    # This means that we can tell if any of the non-essential keys are
    # modified or removed.
    stripped_object = strip_non_essential_keys(event_object)

    # Sign the stripped JSON object. The signature only covers the
    # essential keys and the hashes. This means that we can check the
    # signature even if the event is redacted.
    signed_object = sign_json(stripped_object, signing_key, signing_name)

    # Copy the signatures from the stripped event to the original event.
    event_object["signatures"] = signed_object["signatures"]

def compute_content_hash(event_object):
    # take a copy of the event before we remove any keys.
    event_object = dict(event_object)

    # Keys under "unsigned" can be modified by other servers.
    # They are useful for conveying information like the age of an
    # event that will change in transit.
    # Since they can be modified we need to exclude them from the hash.
    event_object.pop("unsigned", None)

    # Signatures will depend on the current value of the "hashes" key.
    # We cannot add new hashes without invalidating existing signatures.
    event_object.pop("signatures", None)

    # The "hashes" key might contain multiple algorithms if we decide to
    # migrate away from SHA-2. We don't want to include an existing hash
    # output in our hash so we exclude the "hashes" dict from the hash.
    event_object.pop("hashes", None)

    # Encode the JSON using a canonical encoding so that we get the same
    # bytes on every server for the same JSON object.
    event_json_bytes = encode_canonical_json(event_object)

    return hashlib.sha256(event_json_bytes)
```

## Security considerations

When a domain's ownership changes, the new controller of the domain can
masquerade as the previous owner, receiving messages (similarly to
email) and request past messages from other servers. In the future,
proposals like
[MSC1228](https://github.com/matrix-org/matrix-spec-proposals/issues/1228) will
address this issue.
