summary refs log tree commit diff
path: root/v1.56/print.html
diff options
context:
space:
mode:
Diffstat (limited to 'v1.56/print.html')
-rw-r--r--v1.56/print.html15093
1 files changed, 15093 insertions, 0 deletions
diff --git a/v1.56/print.html b/v1.56/print.html
new file mode 100644
index 0000000000..4066fdf5fe
--- /dev/null
+++ b/v1.56/print.html
@@ -0,0 +1,15093 @@
+<!DOCTYPE HTML>
+<html lang="en" class="sidebar-visible no-js light">
+    <head>
+        <!-- Book generated using mdBook -->
+        <meta charset="UTF-8">
+        <title>Synapse</title>
+        
+        <meta name="robots" content="noindex" />
+        
+        
+
+
+        <!-- Custom HTML head -->
+        
+
+
+        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
+        <meta name="description" content="">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <meta name="theme-color" content="#ffffff" />
+
+        
+        <link rel="icon" href="favicon.svg">
+        
+        
+        <link rel="shortcut icon" href="favicon.png">
+        
+        <link rel="stylesheet" href="css/variables.css">
+        <link rel="stylesheet" href="css/general.css">
+        <link rel="stylesheet" href="css/chrome.css">
+        
+        <link rel="stylesheet" href="css/print.css" media="print">
+        
+
+        <!-- Fonts -->
+        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
+        
+        <link rel="stylesheet" href="fonts/fonts.css">
+        
+
+        <!-- Highlight.js Stylesheets -->
+        <link rel="stylesheet" href="highlight.css">
+        <link rel="stylesheet" href="tomorrow-night.css">
+        <link rel="stylesheet" href="ayu-highlight.css">
+
+        <!-- Custom theme stylesheets -->
+        
+        <link rel="stylesheet" href="docs/website_files/table-of-contents.css">
+        
+        <link rel="stylesheet" href="docs/website_files/remove-nav-buttons.css">
+        
+        <link rel="stylesheet" href="docs/website_files/indent-section-headers.css">
+        
+
+        
+    </head>
+    <body>
+        <!-- Provide site root to javascript -->
+        <script type="text/javascript">
+            var path_to_root = "";
+            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
+        </script>
+
+        <!-- Work around some values being stored in localStorage wrapped in quotes -->
+        <script type="text/javascript">
+            try {
+                var theme = localStorage.getItem('mdbook-theme');
+                var sidebar = localStorage.getItem('mdbook-sidebar');
+                if (theme.startsWith('"') && theme.endsWith('"')) {
+                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
+                }
+                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
+                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
+                }
+            } catch (e) { }
+        </script>
+
+        <!-- Set the theme before any content is loaded, prevents flash -->
+        <script type="text/javascript">
+            var theme;
+            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
+            if (theme === null || theme === undefined) { theme = default_theme; }
+            var html = document.querySelector('html');
+            html.classList.remove('no-js')
+            html.classList.remove('light')
+            html.classList.add(theme);
+            html.classList.add('js');
+        </script>
+
+        <!-- Hide / unhide sidebar before it is displayed -->
+        <script type="text/javascript">
+            var html = document.querySelector('html');
+            var sidebar = 'hidden';
+            if (document.body.clientWidth >= 1080) {
+                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
+                sidebar = sidebar || 'visible';
+            }
+            html.classList.remove('sidebar-visible');
+            html.classList.add("sidebar-" + sidebar);
+        </script>
+
+        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
+            <div class="sidebar-scrollbox">
+                <ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">Introduction</li><li class="chapter-item expanded "><a href="welcome_and_overview.html">Welcome and Overview</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html">Installation</a></li><li class="chapter-item expanded "><a href="postgres.html">Using Postgres</a></li><li class="chapter-item expanded "><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></li><li class="chapter-item expanded "><a href="setup/forward_proxy.html">Configuring a Forward/Outbound Proxy</a></li><li class="chapter-item expanded "><a href="turn-howto.html">Configuring a Turn Server</a></li><li class="chapter-item expanded "><a href="delegate.html">Delegation</a></li><li class="chapter-item expanded affix "><li class="part-title">Upgrading</li><li class="chapter-item expanded "><a href="upgrade.html">Upgrading between Synapse Versions</a></li><li class="chapter-item expanded affix "><li class="part-title">Usage</li><li class="chapter-item expanded "><a href="federate.html">Federation</a></li><li class="chapter-item expanded "><a href="usage/configuration/index.html">Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></li><li class="chapter-item expanded "><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></li><li class="chapter-item expanded "><a href="structured_logging.html">Structured Logging</a></li><li class="chapter-item expanded "><a href="templates.html">Templates</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/index.html">User Authentication</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/single_sign_on/index.html">Single-Sign On</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="openid.html">OpenID Connect</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/single_sign_on/saml.html">SAML</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/single_sign_on/cas.html">CAS</a></li><li class="chapter-item expanded "><a href="sso_mapping_providers.html">SSO Mapping Providers</a></li></ol></li><li class="chapter-item expanded "><a href="password_auth_providers.html">Password Auth Providers</a></li><li class="chapter-item expanded "><a href="jwt.html">JSON Web Tokens</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/refresh_tokens.html">Refresh Tokens</a></li></ol></li><li class="chapter-item expanded "><a href="CAPTCHA_SETUP.html">Registration Captcha</a></li><li class="chapter-item expanded "><a href="application_services.html">Application Services</a></li><li class="chapter-item expanded "><a href="server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="consent_tracking.html">Consent Tracking</a></li><li class="chapter-item expanded "><a href="development/url_previews.html">URL Previews</a></li><li class="chapter-item expanded "><a href="user_directory.html">User Directory</a></li><li class="chapter-item expanded "><a href="message_retention_policies.html">Message Retention Policies</a></li><li class="chapter-item expanded "><a href="modules/index.html">Pluggable Modules</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="modules/writing_a_module.html">Writing a module</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="modules/spam_checker_callbacks.html">Spam checker callbacks</a></li><li class="chapter-item expanded "><a href="modules/third_party_rules_callbacks.html">Third-party rules callbacks</a></li><li class="chapter-item expanded "><a href="modules/presence_router_callbacks.html">Presence router callbacks</a></li><li class="chapter-item expanded "><a href="modules/account_validity_callbacks.html">Account validity callbacks</a></li><li class="chapter-item expanded "><a href="modules/password_auth_provider_callbacks.html">Password auth provider callbacks</a></li><li class="chapter-item expanded "><a href="modules/background_update_controller_callbacks.html">Background update controller callbacks</a></li><li class="chapter-item expanded "><a href="modules/porting_legacy_module.html">Porting a legacy module to the new interface</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="workers.html">Workers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="synctl_workers.html">Using synctl with Workers</a></li><li class="chapter-item expanded "><a href="systemd-with-workers/index.html">Systemd</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="usage/administration/index.html">Administration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/administration/admin_api/index.html">Admin API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="admin_api/account_validity.html">Account Validity</a></li><li class="chapter-item expanded "><a href="usage/administration/admin_api/background_updates.html">Background Updates</a></li><li class="chapter-item expanded "><a href="admin_api/delete_group.html">Delete Group</a></li><li class="chapter-item expanded "><a href="admin_api/event_reports.html">Event Reports</a></li><li class="chapter-item expanded "><a href="admin_api/media_admin_api.html">Media</a></li><li class="chapter-item expanded "><a href="admin_api/purge_history_api.html">Purge History</a></li><li class="chapter-item expanded "><a href="admin_api/register_api.html">Register Users</a></li><li class="chapter-item expanded "><a href="usage/administration/admin_api/registration_tokens.html">Registration Tokens</a></li><li class="chapter-item expanded "><a href="admin_api/room_membership.html">Manipulate Room Membership</a></li><li class="chapter-item expanded "><a href="admin_api/rooms.html">Rooms</a></li><li class="chapter-item expanded "><a href="admin_api/server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="admin_api/statistics.html">Statistics</a></li><li class="chapter-item expanded "><a href="admin_api/user_admin_api.html">Users</a></li><li class="chapter-item expanded "><a href="admin_api/version_api.html">Server Version</a></li><li class="chapter-item expanded "><a href="usage/administration/admin_api/federation.html">Federation</a></li></ol></li><li class="chapter-item expanded "><a href="manhole.html">Manhole</a></li><li class="chapter-item expanded "><a href="metrics-howto.html">Monitoring</a></li><li class="chapter-item expanded "><a href="usage/administration/understanding_synapse_through_grafana_graphs.html">Understanding Synapse Through Grafana Graphs</a></li><li class="chapter-item expanded "><a href="usage/administration/useful_sql_for_admins.html">Useful SQL for Admins</a></li><li class="chapter-item expanded "><a href="usage/administration/database_maintenance_tools.html">Database Maintenance Tools</a></li><li class="chapter-item expanded "><a href="usage/administration/state_groups.html">State Groups</a></li><li class="chapter-item expanded "><a href="usage/administration/request_log.html">Request log format</a></li><li class="chapter-item expanded "><a href="usage/administration/admin_faq.html">Admin FAQ</a></li><li class="chapter-item expanded "><div>Scripts</div></li></ol></li><li class="chapter-item expanded "><li class="part-title">Development</li><li class="chapter-item expanded "><a href="development/contributing_guide.html">Contributing Guide</a></li><li class="chapter-item expanded "><a href="code_style.html">Code Style</a></li><li class="chapter-item expanded "><a href="development/releases.html">Release Cycle</a></li><li class="chapter-item expanded "><a href="development/git.html">Git Usage</a></li><li class="chapter-item expanded "><div>Testing</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="development/demo.html">Demo scripts</a></li></ol></li><li class="chapter-item expanded "><a href="opentracing.html">OpenTracing</a></li><li class="chapter-item expanded "><a href="development/database_schema.html">Database Schemas</a></li><li class="chapter-item expanded "><a href="development/experimental_features.html">Experimental features</a></li><li class="chapter-item expanded "><div>Synapse Architecture</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="log_contexts.html">Log Contexts</a></li><li class="chapter-item expanded "><a href="replication.html">Replication</a></li><li class="chapter-item expanded "><a href="tcp_replication.html">TCP Replication</a></li></ol></li><li class="chapter-item expanded "><a href="development/internal_documentation/index.html">Internal Documentation</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single Sign-On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="development/saml.html">SAML</a></li><li class="chapter-item expanded "><a href="development/cas.html">CAS</a></li></ol></li><li class="chapter-item expanded "><a href="development/room-dag-concepts.html">Room DAG concepts</a></li><li class="chapter-item expanded "><div>State Resolution</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="auth_chain_difference_algorithm.html">The Auth Chain Difference Algorithm</a></li></ol></li><li class="chapter-item expanded "><a href="media_repository.html">Media Repository</a></li><li class="chapter-item expanded "><a href="room_and_user_statistics.html">Room and User Statistics</a></li></ol></li><li class="chapter-item expanded "><div>Scripts</div></li><li class="chapter-item expanded affix "><li class="part-title">Other</li><li class="chapter-item expanded "><a href="deprecation_policy.html">Dependency Deprecation Policy</a></li><li class="chapter-item expanded "><a href="other/running_synapse_on_single_board_computers.html">Running Synapse on a Single-Board Computer</a></li></ol>
+            </div>
+            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
+        </nav>
+
+        <div id="page-wrapper" class="page-wrapper">
+
+            <div class="page">
+                
+                <div id="menu-bar-hover-placeholder"></div>
+                <div id="menu-bar" class="menu-bar sticky bordered">
+                    <div class="left-buttons">
+                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
+                            <i class="fa fa-bars"></i>
+                        </button>
+                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
+                            <i class="fa fa-paint-brush"></i>
+                        </button>
+                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
+                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
+                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
+                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
+                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
+                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
+                        </ul>
+                        
+                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
+                            <i class="fa fa-search"></i>
+                        </button>
+                        
+                    </div>
+
+                    <h1 class="menu-title">Synapse</h1>
+
+                    <div class="right-buttons">
+                        
+                        <a href="print.html" title="Print this book" aria-label="Print this book">
+                            <i id="print-button" class="fa fa-print"></i>
+                        </a>
+                        
+                        
+                        <a href="https://github.com/matrix-org/synapse" title="Git repository" aria-label="Git repository">
+                            <i id="git-repository-button" class="fa fa-github"></i>
+                        </a>
+                        
+                        
+
+                    </div>
+                </div>
+
+                
+                <div id="search-wrapper" class="hidden">
+                    <form id="searchbar-outer" class="searchbar-outer">
+                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
+                    </form>
+                    <div id="searchresults-outer" class="searchresults-outer hidden">
+                        <div id="searchresults-header" class="searchresults-header"></div>
+                        <ul id="searchresults">
+                        </ul>
+                    </div>
+                </div>
+                
+
+                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
+                <script type="text/javascript">
+                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
+                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
+                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
+                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
+                    });
+                </script>
+
+                <div id="content" class="content">
+                    <main>
+                        <!-- Page table of contents -->
+                        <div class="sidetoc">
+                            <nav class="pagetoc"></nav>
+                        </div>
+
+                        <div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
+<p>Welcome to the documentation repository for Synapse, a 
+<a href="https://matrix.org">Matrix</a> homeserver implementation developed by the matrix.org core 
+team.</p>
+<h2 id="installing-and-using-synapse"><a class="header" href="#installing-and-using-synapse">Installing and using Synapse</a></h2>
+<p>This documentation covers topics for <strong>installation</strong>, <strong>configuration</strong> and
+<strong>maintainence</strong> of your Synapse process:</p>
+<ul>
+<li>
+<p>Learn how to <a href="setup/installation.html">install</a> and
+<a href="usage/configuration/index.html">configure</a> your own instance, perhaps with <a href="usage/configuration/user_authentication/index.html">Single
+Sign-On</a>.</p>
+</li>
+<li>
+<p>See how to <a href="upgrade.html">upgrade</a> between Synapse versions.</p>
+</li>
+<li>
+<p>Administer your instance using the <a href="usage/administration/admin_api/index.html">Admin
+API</a>, installing <a href="modules/index.html">pluggable
+modules</a>, or by accessing the <a href="manhole.html">manhole</a>.</p>
+</li>
+<li>
+<p>Learn how to <a href="usage/administration/request_log.html">read log lines</a>, configure
+<a href="usage/configuration/logging_sample_config.html">logging</a> or set up <a href="structured_logging.html">structured
+logging</a>.</p>
+</li>
+<li>
+<p>Scale Synapse through additional <a href="workers.html">worker processes</a>.</p>
+</li>
+<li>
+<p>Set up <a href="metrics-howto.html">monitoring and metrics</a> to keep an eye on your
+Synapse instance's performance.</p>
+</li>
+</ul>
+<h2 id="developing-on-synapse"><a class="header" href="#developing-on-synapse">Developing on Synapse</a></h2>
+<p>Contributions are welcome! Synapse is primarily written in
+<a href="https://python.org">Python</a>. As a developer, you may be interested in the
+following documentation:</p>
+<ul>
+<li>
+<p>Read the <a href="development/contributing_guide.html">Contributing Guide</a>. It is meant
+to walk new contributors through the process of developing and submitting a
+change to the Synapse codebase (which is <a href="https://github.com/matrix-org/synapse">hosted on
+GitHub</a>).</p>
+</li>
+<li>
+<p>Set up your <a href="development/contributing_guide.html#2-what-do-i-need">development
+environment</a>, then learn
+how to <a href="development/contributing_guide.html#run-the-linters">lint</a> and
+<a href="development/contributing_guide.html#8-test-test-test">test</a> your code.</p>
+</li>
+<li>
+<p>Look at <a href="https://github.com/matrix-org/synapse/issues">the issue tracker</a> for
+bugs to fix or features to add. If you're new, it may be best to start with
+those labeled <a href="https://github.com/matrix-org/synapse/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22">good first
+issue</a>.</p>
+</li>
+<li>
+<p>Understand <a href="development/internal_documentation/index.html">how Synapse is
+built</a>, how to <a href="development/database_schema.html">migrate
+database schemas</a>, learn about
+<a href="federate.html">federation</a> and how to <a href="federate.html#running-a-demo-federation-of-synapses">set up a local
+federation</a> for development.</p>
+</li>
+<li>
+<p>We like to keep our <code>git</code> history clean. <a href="development/git.html">Learn</a> how to
+do so!</p>
+</li>
+<li>
+<p>And finally, contribute to this documentation! The source for which is
+<a href="https://github.com/matrix-org/synapse/tree/develop/docs">located here</a>.</p>
+</li>
+</ul>
+<h2 id="donating-to-synapse-development"><a class="header" href="#donating-to-synapse-development">Donating to Synapse development</a></h2>
+<p>Want to help keep Synapse going but don't know how to code? Synapse is a
+<a href="https://matrix.org">Matrix.org Foundation</a> project. Consider becoming a
+supportor on <a href="https://liberapay.com/matrixdotorg">Liberapay</a>,
+<a href="https://patreon.com/matrixdotorg">Patreon</a> or through
+<a href="https://paypal.me/matrixdotorg">PayPal</a> via a one-time donation.</p>
+<p>If you are an organisation or enterprise and would like to sponsor development,
+reach out to us over email at: support (at) matrix.org</p>
+<h2 id="reporting-a-security-vulnerability"><a class="header" href="#reporting-a-security-vulnerability">Reporting a security vulnerability</a></h2>
+<p>If you've found a security issue in Synapse or any other Matrix.org Foundation
+project, please report it to us in accordance with our <a href="https://www.matrix.org/security-disclosure-policy/">Security Disclosure
+Policy</a>. Thank you!</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="installation-instructions"><a class="header" href="#installation-instructions">Installation Instructions</a></h1>
+<h2 id="choosing-your-server-name"><a class="header" href="#choosing-your-server-name">Choosing your server name</a></h2>
+<p>It is important to choose the name for your server before you install Synapse,
+because it cannot be changed later.</p>
+<p>The server name determines the &quot;domain&quot; part of user-ids for users on your
+server: these will all be of the format <code>@user:my.domain.name</code>. It also
+determines how other matrix servers will reach yours for federation.</p>
+<p>For a test configuration, set this to the hostname of your server. For a more
+production-ready setup, you will probably want to specify your domain
+(<code>example.com</code>) rather than a matrix-specific hostname here (in the same way
+that your email address is probably <code>user@example.com</code> rather than
+<code>user@email.example.com</code>) - but doing so may require more advanced setup: see
+<a href="setup/../federate.html">Setting up Federation</a>.</p>
+<h2 id="installing-synapse"><a class="header" href="#installing-synapse">Installing Synapse</a></h2>
+<h3 id="prebuilt-packages"><a class="header" href="#prebuilt-packages">Prebuilt packages</a></h3>
+<p>Prebuilt packages are available for a number of platforms. These are recommended
+for most users.</p>
+<h4 id="docker-images-and-ansible-playbooks"><a class="header" href="#docker-images-and-ansible-playbooks">Docker images and Ansible playbooks</a></h4>
+<p>There is an official synapse image available at
+<a href="https://hub.docker.com/r/matrixdotorg/synapse">https://hub.docker.com/r/matrixdotorg/synapse</a> which can be used with
+the docker-compose file available at
+<a href="https://github.com/matrix-org/synapse/tree/develop/contrib/docker">contrib/docker</a>.
+Further information on this including configuration options is available in the README
+on hub.docker.com.</p>
+<p>Alternatively, Andreas Peters (previously Silvio Fricke) has contributed a
+Dockerfile to automate a synapse server in a single Docker image, at
+<a href="https://hub.docker.com/r/avhost/docker-matrix/tags/">https://hub.docker.com/r/avhost/docker-matrix/tags/</a></p>
+<p>Slavi Pantaleev has created an Ansible playbook,
+which installs the offical Docker image of Matrix Synapse
+along with many other Matrix-related services (Postgres database, Element, coturn,
+ma1sd, SSL support, etc.).
+For more details, see
+<a href="https://github.com/spantaleev/matrix-docker-ansible-deploy">https://github.com/spantaleev/matrix-docker-ansible-deploy</a></p>
+<h4 id="debianubuntu"><a class="header" href="#debianubuntu">Debian/Ubuntu</a></h4>
+<h5 id="matrixorg-packages"><a class="header" href="#matrixorg-packages">Matrix.org packages</a></h5>
+<p>Matrix.org provides Debian/Ubuntu packages of Synapse, for the amd64
+architecture via <a href="https://packages.matrix.org/debian/">https://packages.matrix.org/debian/</a>.</p>
+<p>To install the latest release:</p>
+<pre><code class="language-sh">sudo apt install -y lsb-release wget apt-transport-https
+sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
+echo &quot;deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main&quot; |
+    sudo tee /etc/apt/sources.list.d/matrix-org.list
+sudo apt update
+sudo apt install matrix-synapse-py3
+</code></pre>
+<p>Packages are also published for release candidates. To enable the prerelease
+channel, add <code>prerelease</code> to the <code>sources.list</code> line. For example:</p>
+<pre><code class="language-sh">sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
+echo &quot;deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main prerelease&quot; |
+    sudo tee /etc/apt/sources.list.d/matrix-org.list
+sudo apt update
+sudo apt install matrix-synapse-py3
+</code></pre>
+<p>The fingerprint of the repository signing key (as shown by <code>gpg /usr/share/keyrings/matrix-org-archive-keyring.gpg</code>) is
+<code>AAF9AE843A7584B5A3E4CD2BCF45A512DE2DA058</code>.</p>
+<p>When installing with Debian packages, you might prefer to place files in
+<code>/etc/matrix-synapse/conf.d/</code> to override your configuration without editing
+the main configuration file at <code>/etc/matrix-synapse/homeserver.yaml</code>.
+By doing that, you won't be asked if you want to replace your configuration
+file when you upgrade the Debian package to a later version.</p>
+<h5 id="downstream-debian-packages"><a class="header" href="#downstream-debian-packages">Downstream Debian packages</a></h5>
+<p>We do not recommend using the packages from the default Debian <code>buster</code>
+repository at this time, as they are old and suffer from known security
+vulnerabilities. You can install the latest version of Synapse from
+<a href="setup/installation.html#matrixorg-packages">our repository</a> or from <code>buster-backports</code>. Please
+see the <a href="https://backports.debian.org/Instructions/">Debian documentation</a>
+for information on how to use backports.</p>
+<p>If you are using Debian <code>sid</code> or testing, Synapse is available in the default
+repositories and it should be possible to install it simply with:</p>
+<pre><code class="language-sh">sudo apt install matrix-synapse
+</code></pre>
+<h5 id="downstream-ubuntu-packages"><a class="header" href="#downstream-ubuntu-packages">Downstream Ubuntu packages</a></h5>
+<p>We do not recommend using the packages in the default Ubuntu repository
+at this time, as they are old and suffer from known security vulnerabilities.
+The latest version of Synapse can be installed from <a href="setup/installation.html#matrixorg-packages">our repository</a>.</p>
+<h4 id="fedora"><a class="header" href="#fedora">Fedora</a></h4>
+<p>Synapse is in the Fedora repositories as <code>matrix-synapse</code>:</p>
+<pre><code class="language-sh">sudo dnf install matrix-synapse
+</code></pre>
+<p>Oleg Girko provides Fedora RPMs at
+<a href="https://obs.infoserver.lv/project/monitor/matrix-synapse">https://obs.infoserver.lv/project/monitor/matrix-synapse</a></p>
+<h4 id="opensuse"><a class="header" href="#opensuse">OpenSUSE</a></h4>
+<p>Synapse is in the OpenSUSE repositories as <code>matrix-synapse</code>:</p>
+<pre><code class="language-sh">sudo zypper install matrix-synapse
+</code></pre>
+<h4 id="suse-linux-enterprise-server"><a class="header" href="#suse-linux-enterprise-server">SUSE Linux Enterprise Server</a></h4>
+<p>Unofficial package are built for SLES 15 in the openSUSE:Backports:SLE-15 repository at
+<a href="https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15/standard/">https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15/standard/</a></p>
+<h4 id="archlinux"><a class="header" href="#archlinux">ArchLinux</a></h4>
+<p>The quickest way to get up and running with ArchLinux is probably with the community package
+<a href="https://www.archlinux.org/packages/community/any/matrix-synapse/">https://www.archlinux.org/packages/community/any/matrix-synapse/</a>, which should pull in most of
+the necessary dependencies.</p>
+<p>pip may be outdated (6.0.7-1 and needs to be upgraded to 6.0.8-1 ):</p>
+<pre><code class="language-sh">sudo pip install --upgrade pip
+</code></pre>
+<p>If you encounter an error with lib bcrypt causing an Wrong ELF Class:
+ELFCLASS32 (x64 Systems), you may need to reinstall py-bcrypt to correctly
+compile it under the right architecture. (This should not be needed if
+installing under virtualenv):</p>
+<pre><code class="language-sh">sudo pip uninstall py-bcrypt
+sudo pip install py-bcrypt
+</code></pre>
+<h4 id="void-linux"><a class="header" href="#void-linux">Void Linux</a></h4>
+<p>Synapse can be found in the void repositories as 'synapse':</p>
+<pre><code class="language-sh">xbps-install -Su
+xbps-install -S synapse
+</code></pre>
+<h4 id="freebsd"><a class="header" href="#freebsd">FreeBSD</a></h4>
+<p>Synapse can be installed via FreeBSD Ports or Packages contributed by Brendan Molloy from:</p>
+<ul>
+<li>Ports: <code>cd /usr/ports/net-im/py-matrix-synapse &amp;&amp; make install clean</code></li>
+<li>Packages: <code>pkg install py38-matrix-synapse</code></li>
+</ul>
+<h4 id="openbsd"><a class="header" href="#openbsd">OpenBSD</a></h4>
+<p>As of OpenBSD 6.7 Synapse is available as a pre-compiled binary. The filesystem
+underlying the homeserver directory (defaults to <code>/var/synapse</code>) has to be
+mounted with <code>wxallowed</code> (cf. <code>mount(8)</code>), so creating a separate filesystem
+and mounting it to <code>/var/synapse</code> should be taken into consideration.</p>
+<p>Installing Synapse:</p>
+<pre><code class="language-sh">doas pkg_add synapse
+</code></pre>
+<h4 id="nixos"><a class="header" href="#nixos">NixOS</a></h4>
+<p>Robin Lambertz has packaged Synapse for NixOS at:
+<a href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/misc/matrix-synapse.nix">https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/misc/matrix-synapse.nix</a></p>
+<h3 id="installing-as-a-python-module-from-pypi"><a class="header" href="#installing-as-a-python-module-from-pypi">Installing as a Python module from PyPI</a></h3>
+<p>It's also possible to install Synapse as a Python module from PyPI.</p>
+<p>When following this route please make sure that the <a href="setup/installation.html#platform-specific-prerequisites">Platform-specific prerequisites</a> are already installed.</p>
+<p>System requirements:</p>
+<ul>
+<li>POSIX-compliant system (tested on Linux &amp; OS X)</li>
+<li>Python 3.7 or later, up to Python 3.10.</li>
+<li>At least 1GB of free RAM if you want to join large public rooms like #matrix:matrix.org</li>
+</ul>
+<p>To install the Synapse homeserver run:</p>
+<pre><code class="language-sh">mkdir -p ~/synapse
+virtualenv -p python3 ~/synapse/env
+source ~/synapse/env/bin/activate
+pip install --upgrade pip
+pip install --upgrade setuptools
+pip install matrix-synapse
+</code></pre>
+<p>This will download Synapse from <a href="https://pypi.org/project/matrix-synapse">PyPI</a>
+and install it, along with the python libraries it uses, into a virtual environment
+under <code>~/synapse/env</code>.  Feel free to pick a different directory if you
+prefer.</p>
+<p>This Synapse installation can then be later upgraded by using pip again with the
+update flag:</p>
+<pre><code class="language-sh">source ~/synapse/env/bin/activate
+pip install -U matrix-synapse
+</code></pre>
+<p>Before you can start Synapse, you will need to generate a configuration
+file. To do this, run (in your virtualenv, as before):</p>
+<pre><code class="language-sh">cd ~/synapse
+python -m synapse.app.homeserver \
+    --server-name my.domain.name \
+    --config-path homeserver.yaml \
+    --generate-config \
+    --report-stats=[yes|no]
+</code></pre>
+<p>... substituting an appropriate value for <code>--server-name</code>.</p>
+<p>This command will generate you a config file that you can then customise, but it will
+also generate a set of keys for you. These keys will allow your homeserver to
+identify itself to other homeserver, so don't lose or delete them. It would be
+wise to back them up somewhere safe. (If, for whatever reason, you do need to
+change your homeserver's keys, you may find that other homeserver have the
+old key cached. If you update the signing key, you should change the name of the
+key in the <code>&lt;server name&gt;.signing.key</code> file (the second word) to something
+different. See the <a href="https://matrix.org/docs/spec/server_server/latest.html#retrieving-server-keys">spec</a> for more information on key management).</p>
+<p>To actually run your new homeserver, pick a working directory for Synapse to
+run (e.g. <code>~/synapse</code>), and:</p>
+<pre><code class="language-sh">cd ~/synapse
+source env/bin/activate
+synctl start
+</code></pre>
+<h4 id="platform-specific-prerequisites"><a class="header" href="#platform-specific-prerequisites">Platform-specific prerequisites</a></h4>
+<p>Synapse is written in Python but some of the libraries it uses are written in
+C. So before we can install Synapse itself we need a working C compiler and the
+header files for Python C extensions.</p>
+<h5 id="debianubunturaspbian"><a class="header" href="#debianubunturaspbian">Debian/Ubuntu/Raspbian</a></h5>
+<p>Installing prerequisites on Ubuntu or Debian:</p>
+<pre><code class="language-sh">sudo apt install build-essential python3-dev libffi-dev \
+                     python3-pip python3-setuptools sqlite3 \
+                     libssl-dev virtualenv libjpeg-dev libxslt1-dev
+</code></pre>
+<h5 id="archlinux-1"><a class="header" href="#archlinux-1">ArchLinux</a></h5>
+<p>Installing prerequisites on ArchLinux:</p>
+<pre><code class="language-sh">sudo pacman -S base-devel python python-pip \
+               python-setuptools python-virtualenv sqlite3
+</code></pre>
+<h5 id="centosfedora"><a class="header" href="#centosfedora">CentOS/Fedora</a></h5>
+<p>Installing prerequisites on CentOS or Fedora Linux:</p>
+<pre><code class="language-sh">sudo dnf install libtiff-devel libjpeg-devel libzip-devel freetype-devel \
+                 libwebp-devel libxml2-devel libxslt-devel libpq-devel \
+                 python3-virtualenv libffi-devel openssl-devel python3-devel
+sudo dnf groupinstall &quot;Development Tools&quot;
+</code></pre>
+<h5 id="macos"><a class="header" href="#macos">macOS</a></h5>
+<p>Installing prerequisites on macOS:</p>
+<p>You may need to install the latest Xcode developer tools:</p>
+<pre><code class="language-sh">xcode-select --install
+</code></pre>
+<p>On ARM-based Macs you may need to explicitly install libjpeg which is a pillow dependency. You can use Homebrew (https://brew.sh):</p>
+<pre><code class="language-sh"> brew install jpeg
+</code></pre>
+<p>On macOS Catalina (10.15) you may need to explicitly install OpenSSL
+via brew and inform <code>pip</code> about it so that <code>psycopg2</code> builds:</p>
+<pre><code class="language-sh">brew install openssl@1.1
+export LDFLAGS=&quot;-L/usr/local/opt/openssl/lib&quot;
+export CPPFLAGS=&quot;-I/usr/local/opt/openssl/include&quot;
+</code></pre>
+<h5 id="opensuse-1"><a class="header" href="#opensuse-1">OpenSUSE</a></h5>
+<p>Installing prerequisites on openSUSE:</p>
+<pre><code class="language-sh">sudo zypper in -t pattern devel_basis
+sudo zypper in python-pip python-setuptools sqlite3 python-virtualenv \
+               python-devel libffi-devel libopenssl-devel libjpeg62-devel
+</code></pre>
+<h5 id="openbsd-1"><a class="header" href="#openbsd-1">OpenBSD</a></h5>
+<p>A port of Synapse is available under <code>net/synapse</code>. The filesystem
+underlying the homeserver directory (defaults to <code>/var/synapse</code>) has to be
+mounted with <code>wxallowed</code> (cf. <code>mount(8)</code>), so creating a separate filesystem
+and mounting it to <code>/var/synapse</code> should be taken into consideration.</p>
+<p>To be able to build Synapse's dependency on python the <code>WRKOBJDIR</code>
+(cf. <code>bsd.port.mk(5)</code>) for building python, too, needs to be on a filesystem
+mounted with <code>wxallowed</code> (cf. <code>mount(8)</code>).</p>
+<p>Creating a <code>WRKOBJDIR</code> for building python under <code>/usr/local</code> (which on a
+default OpenBSD installation is mounted with <code>wxallowed</code>):</p>
+<pre><code class="language-sh">doas mkdir /usr/local/pobj_wxallowed
+</code></pre>
+<p>Assuming <code>PORTS_PRIVSEP=Yes</code> (cf. <code>bsd.port.mk(5)</code>) and <code>SUDO=doas</code> are
+configured in <code>/etc/mk.conf</code>:</p>
+<pre><code class="language-sh">doas chown _pbuild:_pbuild /usr/local/pobj_wxallowed
+</code></pre>
+<p>Setting the <code>WRKOBJDIR</code> for building python:</p>
+<pre><code class="language-sh">echo WRKOBJDIR_lang/python/3.7=/usr/local/pobj_wxallowed  \\nWRKOBJDIR_lang/python/2.7=/usr/local/pobj_wxallowed &gt;&gt; /etc/mk.conf
+</code></pre>
+<p>Building Synapse:</p>
+<pre><code class="language-sh">cd /usr/ports/net/synapse
+make install
+</code></pre>
+<h5 id="windows"><a class="header" href="#windows">Windows</a></h5>
+<p>Running Synapse natively on Windows is not officially supported.</p>
+<p>If you wish to run or develop Synapse on Windows, the Windows Subsystem for
+Linux provides a Linux environment which is capable of using the Debian, Fedora,
+or source installation methods. More information about WSL can be found at
+<a href="https://docs.microsoft.com/en-us/windows/wsl/install">https://docs.microsoft.com/en-us/windows/wsl/install</a> for Windows 10/11 and
+<a href="https://docs.microsoft.com/en-us/windows/wsl/install-on-server">https://docs.microsoft.com/en-us/windows/wsl/install-on-server</a> for
+Windows Server.</p>
+<h2 id="setting-up-synapse"><a class="header" href="#setting-up-synapse">Setting up Synapse</a></h2>
+<p>Once you have installed synapse as above, you will need to configure it.</p>
+<h3 id="using-postgresql"><a class="header" href="#using-postgresql">Using PostgreSQL</a></h3>
+<p>By default Synapse uses an <a href="https://sqlite.org/">SQLite</a> database and in doing so trades
+performance for convenience. Almost all installations should opt to use <a href="https://www.postgresql.org">PostgreSQL</a>
+instead. Advantages include:</p>
+<ul>
+<li>significant performance improvements due to the superior threading and
+caching model, smarter query optimiser</li>
+<li>allowing the DB to be run on separate hardware</li>
+</ul>
+<p>For information on how to install and use PostgreSQL in Synapse, please see
+<a href="setup/../postgres.html">Using Postgres</a></p>
+<p>SQLite is only acceptable for testing purposes. SQLite should not be used in
+a production server. Synapse will perform poorly when using
+SQLite, especially when participating in large rooms.</p>
+<h3 id="tls-certificates"><a class="header" href="#tls-certificates">TLS certificates</a></h3>
+<p>The default configuration exposes a single HTTP port on the local
+interface: <code>http://localhost:8008</code>. It is suitable for local testing,
+but for any practical use, you will need Synapse's APIs to be served
+over HTTPS.</p>
+<p>The recommended way to do so is to set up a reverse proxy on port
+<code>8448</code>. You can find documentation on doing so in
+<a href="setup/../reverse_proxy.html">the reverse proxy documentation</a>.</p>
+<p>Alternatively, you can configure Synapse to expose an HTTPS port. To do
+so, you will need to edit <code>homeserver.yaml</code>, as follows:</p>
+<ul>
+<li>First, under the <code>listeners</code> section, uncomment the configuration for the
+TLS-enabled listener. (Remove the hash sign (<code>#</code>) at the start of
+each line). The relevant lines are like this:</li>
+</ul>
+<pre><code class="language-yaml">  - port: 8448
+    type: http
+    tls: true
+    resources:
+      - names: [client, federation]
+</code></pre>
+<ul>
+<li>
+<p>You will also need to uncomment the <code>tls_certificate_path</code> and
+<code>tls_private_key_path</code> lines under the <code>TLS</code> section. You will need to manage
+provisioning of these certificates yourself.</p>
+<p>If you are using your own certificate, be sure to use a <code>.pem</code> file that
+includes the full certificate chain including any intermediate certificates
+(for instance, if using certbot, use <code>fullchain.pem</code> as your certificate, not
+<code>cert.pem</code>).</p>
+</li>
+</ul>
+<p>For a more detailed guide to configuring your server for federation, see
+<a href="setup/../federate.html">Federation</a>.</p>
+<h3 id="client-well-known-uri"><a class="header" href="#client-well-known-uri">Client Well-Known URI</a></h3>
+<p>Setting up the client Well-Known URI is optional but if you set it up, it will
+allow users to enter their full username (e.g. <code>@user:&lt;server_name&gt;</code>) into clients
+which support well-known lookup to automatically configure the homeserver and
+identity server URLs. This is useful so that users don't have to memorize or think
+about the actual homeserver URL you are using.</p>
+<p>The URL <code>https://&lt;server_name&gt;/.well-known/matrix/client</code> should return JSON in
+the following format.</p>
+<pre><code class="language-json">{
+  &quot;m.homeserver&quot;: {
+    &quot;base_url&quot;: &quot;https://&lt;matrix.example.com&gt;&quot;
+  }
+}
+</code></pre>
+<p>It can optionally contain identity server information as well.</p>
+<pre><code class="language-json">{
+  &quot;m.homeserver&quot;: {
+    &quot;base_url&quot;: &quot;https://&lt;matrix.example.com&gt;&quot;
+  },
+  &quot;m.identity_server&quot;: {
+    &quot;base_url&quot;: &quot;https://&lt;identity.example.com&gt;&quot;
+  }
+}
+</code></pre>
+<p>To work in browser based clients, the file must be served with the appropriate
+Cross-Origin Resource Sharing (CORS) headers. A recommended value would be
+<code>Access-Control-Allow-Origin: *</code> which would allow all browser based clients to
+view it.</p>
+<p>In nginx this would be something like:</p>
+<pre><code class="language-nginx">location /.well-known/matrix/client {
+    return 200 '{&quot;m.homeserver&quot;: {&quot;base_url&quot;: &quot;https://&lt;matrix.example.com&gt;&quot;}}';
+    default_type application/json;
+    add_header Access-Control-Allow-Origin *;
+}
+</code></pre>
+<p>You should also ensure the <code>public_baseurl</code> option in <code>homeserver.yaml</code> is set
+correctly. <code>public_baseurl</code> should be set to the URL that clients will use to
+connect to your server. This is the same URL you put for the <code>m.homeserver</code>
+<code>base_url</code> above.</p>
+<pre><code class="language-yaml">public_baseurl: &quot;https://&lt;matrix.example.com&gt;&quot;
+</code></pre>
+<h3 id="email"><a class="header" href="#email">Email</a></h3>
+<p>It is desirable for Synapse to have the capability to send email. This allows
+Synapse to send password reset emails, send verifications when an email address
+is added to a user's account, and send email notifications to users when they
+receive new messages.</p>
+<p>To configure an SMTP server for Synapse, modify the configuration section
+headed <code>email</code>, and be sure to have at least the <code>smtp_host</code>, <code>smtp_port</code>
+and <code>notif_from</code> fields filled out.  You may also need to set <code>smtp_user</code>,
+<code>smtp_pass</code>, and <code>require_transport_security</code>.</p>
+<p>If email is not configured, password reset, registration and notifications via
+email will be disabled.</p>
+<h3 id="registering-a-user"><a class="header" href="#registering-a-user">Registering a user</a></h3>
+<p>The easiest way to create a new user is to do so from a client like <a href="https://element.io/">Element</a>.</p>
+<p>Alternatively, you can do so from the command line. This can be done as follows:</p>
+<ol>
+<li>If synapse was installed via pip, activate the virtualenv as follows (if Synapse was
+installed via a prebuilt package, <code>register_new_matrix_user</code> should already be
+on the search path):
+<pre><code class="language-sh">cd ~/synapse
+source env/bin/activate
+synctl start # if not already running
+</code></pre>
+</li>
+<li>Run the following command:
+<pre><code class="language-sh">register_new_matrix_user -c homeserver.yaml http://localhost:8008
+</code></pre>
+</li>
+</ol>
+<p>This will prompt you to add details for the new user, and will then connect to
+the running Synapse to create the new user. For example:</p>
+<pre><code>New user localpart: erikj
+Password:
+Confirm password:
+Make admin [no]:
+Success!
+</code></pre>
+<p>This process uses a setting <code>registration_shared_secret</code> in
+<code>homeserver.yaml</code>, which is shared between Synapse itself and the
+<code>register_new_matrix_user</code> script. It doesn't matter what it is (a random
+value is generated by <code>--generate-config</code>), but it should be kept secret, as
+anyone with knowledge of it can register users, including admin accounts,
+on your server even if <code>enable_registration</code> is <code>false</code>.</p>
+<h3 id="setting-up-a-turn-server"><a class="header" href="#setting-up-a-turn-server">Setting up a TURN server</a></h3>
+<p>For reliable VoIP calls to be routed via this homeserver, you MUST configure
+a TURN server. See <a href="setup/../turn-howto.html">TURN setup</a> for details.</p>
+<h3 id="url-previews"><a class="header" href="#url-previews">URL previews</a></h3>
+<p>Synapse includes support for previewing URLs, which is disabled by default.  To
+turn it on you must enable the <code>url_preview_enabled: True</code> config parameter
+and explicitly specify the IP ranges that Synapse is not allowed to spider for
+previewing in the <code>url_preview_ip_range_blacklist</code> configuration parameter.
+This is critical from a security perspective to stop arbitrary Matrix users
+spidering 'internal' URLs on your network. At the very least we recommend that
+your loopback and RFC1918 IP addresses are blacklisted.</p>
+<p>This also requires the optional <code>lxml</code> python dependency to be  installed. This
+in turn requires the <code>libxml2</code> library to be available - on  Debian/Ubuntu this
+means <code>apt-get install libxml2-dev</code>, or equivalent for your OS.</p>
+<h3 id="troubleshooting-installation"><a class="header" href="#troubleshooting-installation">Troubleshooting Installation</a></h3>
+<p><code>pip</code> seems to leak <em>lots</em> of memory during installation. For instance, a Linux
+host with 512MB of RAM may run out of memory whilst installing Twisted. If this
+happens, you will have to individually install the dependencies which are
+failing, e.g.:</p>
+<pre><code class="language-sh">pip install twisted
+</code></pre>
+<p>If you have any other problems, feel free to ask in
+<a href="https://matrix.to/#/#synapse:matrix.org">#synapse:matrix.org</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="using-postgres"><a class="header" href="#using-postgres">Using Postgres</a></h1>
+<p>Synapse supports PostgreSQL versions 10 or later.</p>
+<h2 id="install-postgres-client-libraries"><a class="header" href="#install-postgres-client-libraries">Install postgres client libraries</a></h2>
+<p>Synapse will require the python postgres client library in order to
+connect to a postgres database.</p>
+<ul>
+<li>
+<p>If you are using the <a href="setup/installation.html#matrixorg-packages">matrix.org debian/ubuntu
+packages</a>, the necessary python
+library will already be installed, but you will need to ensure the
+low-level postgres library is installed, which you can do with
+<code>apt install libpq5</code>.</p>
+</li>
+<li>
+<p>For other pre-built packages, please consult the documentation from
+the relevant package.</p>
+</li>
+<li>
+<p>If you installed synapse <a href="setup/installation.html#installing-from-source">in a
+virtualenv</a>, you can install
+the library with:</p>
+<pre><code>~/synapse/env/bin/pip install &quot;matrix-synapse[postgres]&quot;
+</code></pre>
+<p>(substituting the path to your virtualenv for <code>~/synapse/env</code>, if
+you used a different path). You will require the postgres
+development files. These are in the <code>libpq-dev</code> package on
+Debian-derived distributions.</p>
+</li>
+</ul>
+<h2 id="set-up-database"><a class="header" href="#set-up-database">Set up database</a></h2>
+<p>Assuming your PostgreSQL database user is called <code>postgres</code>, first authenticate as the database user with:</p>
+<pre><code class="language-sh">su - postgres
+# Or, if your system uses sudo to get administrative rights
+sudo -u postgres bash
+</code></pre>
+<p>Then, create a postgres user and a database with:</p>
+<pre><code class="language-sh"># this will prompt for a password for the new user
+createuser --pwprompt synapse_user
+
+createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapse
+</code></pre>
+<p>The above will create a user called <code>synapse_user</code>, and a database called
+<code>synapse</code>.</p>
+<p>Note that the PostgreSQL database <em>must</em> have the correct encoding set
+(as shown above), otherwise it will not be able to store UTF8 strings.</p>
+<p>You may need to enable password authentication so <code>synapse_user</code> can
+connect to the database. See
+<a href="https://www.postgresql.org/docs/current/auth-pg-hba-conf.html">https://www.postgresql.org/docs/current/auth-pg-hba-conf.html</a>.</p>
+<h2 id="synapse-config"><a class="header" href="#synapse-config">Synapse config</a></h2>
+<p>When you are ready to start using PostgreSQL, edit the <code>database</code>
+section in your config file to match the following lines:</p>
+<pre><code class="language-yaml">database:
+  name: psycopg2
+  args:
+    user: &lt;user&gt;
+    password: &lt;pass&gt;
+    database: &lt;db&gt;
+    host: &lt;host&gt;
+    cp_min: 5
+    cp_max: 10
+</code></pre>
+<p>All key, values in <code>args</code> are passed to the <code>psycopg2.connect(..)</code>
+function, except keys beginning with <code>cp_</code>, which are consumed by the
+twisted adbapi connection pool. See the <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS">libpq
+documentation</a>
+for a list of options which can be passed.</p>
+<p>You should consider tuning the <code>args.keepalives_*</code> options if there is any danger of
+the connection between your homeserver and database dropping, otherwise Synapse
+may block for an extended period while it waits for a response from the
+database server. Example values might be:</p>
+<pre><code class="language-yaml">database:
+  args:
+    # ... as above
+
+    # seconds of inactivity after which TCP should send a keepalive message to the server
+    keepalives_idle: 10
+
+    # the number of seconds after which a TCP keepalive message that is not
+    # acknowledged by the server should be retransmitted
+    keepalives_interval: 10
+
+    # the number of TCP keepalives that can be lost before the client's connection
+    # to the server is considered dead
+    keepalives_count: 3
+</code></pre>
+<h2 id="tuning-postgres"><a class="header" href="#tuning-postgres">Tuning Postgres</a></h2>
+<p>The default settings should be fine for most deployments. For larger
+scale deployments tuning some of the settings is recommended, details of
+which can be found at
+<a href="https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server">https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server</a>.</p>
+<p>In particular, we've found tuning the following values helpful for
+performance:</p>
+<ul>
+<li><code>shared_buffers</code></li>
+<li><code>effective_cache_size</code></li>
+<li><code>work_mem</code></li>
+<li><code>maintenance_work_mem</code></li>
+<li><code>autovacuum_work_mem</code></li>
+</ul>
+<p>Note that the appropriate values for those fields depend on the amount
+of free memory the database host has available.</p>
+<p>Additionally, admins of large deployments might want to consider using huge pages
+to help manage memory, especially when using large values of <code>shared_buffers</code>. You
+can read more about that <a href="https://www.postgresql.org/docs/10/kernel-resources.html#LINUX-HUGE-PAGES">here</a>.</p>
+<h2 id="porting-from-sqlite"><a class="header" href="#porting-from-sqlite">Porting from SQLite</a></h2>
+<h3 id="overview"><a class="header" href="#overview">Overview</a></h3>
+<p>The script <code>synapse_port_db</code> allows porting an existing synapse server
+backed by SQLite to using PostgreSQL. This is done in as a two phase
+process:</p>
+<ol>
+<li>Copy the existing SQLite database to a separate location and run
+the port script against that offline database.</li>
+<li>Shut down the server. Rerun the port script to port any data that
+has come in since taking the first snapshot. Restart server against
+the PostgreSQL database.</li>
+</ol>
+<p>The port script is designed to be run repeatedly against newer snapshots
+of the SQLite database file. This makes it safe to repeat step 1 if
+there was a delay between taking the previous snapshot and being ready
+to do step 2.</p>
+<p>It is safe to at any time kill the port script and restart it.</p>
+<p>Note that the database may take up significantly more (25% - 100% more)
+space on disk after porting to Postgres.</p>
+<h3 id="using-the-port-script"><a class="header" href="#using-the-port-script">Using the port script</a></h3>
+<p>Firstly, shut down the currently running synapse server and copy its
+database file (typically <code>homeserver.db</code>) to another location. Once the
+copy is complete, restart synapse. For instance:</p>
+<pre><code class="language-sh">synctl stop
+cp homeserver.db homeserver.db.snapshot
+synctl start
+</code></pre>
+<p>Copy the old config file into a new config file:</p>
+<pre><code class="language-sh">cp homeserver.yaml homeserver-postgres.yaml
+</code></pre>
+<p>Edit the database section as described in the section <em>Synapse config</em>
+above and with the SQLite snapshot located at <code>homeserver.db.snapshot</code>
+simply run:</p>
+<pre><code class="language-sh">synapse_port_db --sqlite-database homeserver.db.snapshot \
+    --postgres-config homeserver-postgres.yaml
+</code></pre>
+<p>The flag <code>--curses</code> displays a coloured curses progress UI.</p>
+<p>If the script took a long time to complete, or time has otherwise passed
+since the original snapshot was taken, repeat the previous steps with a
+newer snapshot.</p>
+<p>To complete the conversion shut down the synapse server and run the port
+script one last time, e.g. if the SQLite database is at <code>homeserver.db</code>
+run:</p>
+<pre><code class="language-sh">synapse_port_db --sqlite-database homeserver.db \
+    --postgres-config homeserver-postgres.yaml
+</code></pre>
+<p>Once that has completed, change the synapse config to point at the
+PostgreSQL database configuration file <code>homeserver-postgres.yaml</code>:</p>
+<pre><code class="language-sh">synctl stop
+mv homeserver.yaml homeserver-old-sqlite.yaml
+mv homeserver-postgres.yaml homeserver.yaml
+synctl start
+</code></pre>
+<p>Synapse should now be running against PostgreSQL.</p>
+<h2 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
+<h3 id="alternative-auth-methods"><a class="header" href="#alternative-auth-methods">Alternative auth methods</a></h3>
+<p>If you get an error along the lines of <code>FATAL:  Ident authentication failed for user &quot;synapse_user&quot;</code>, you may need to use an authentication method other than
+<code>ident</code>:</p>
+<ul>
+<li>
+<p>If the <code>synapse_user</code> user has a password, add the password to the <code>database:</code>
+section of <code>homeserver.yaml</code>. Then add the following to <code>pg_hba.conf</code>:</p>
+<pre><code>host    synapse     synapse_user    ::1/128     md5  # or `scram-sha-256` instead of `md5` if you use that
+</code></pre>
+</li>
+<li>
+<p>If the <code>synapse_user</code> user does not have a password, then a password doesn't
+have to be added to <code>homeserver.yaml</code>. But the following does need to be added
+to <code>pg_hba.conf</code>:</p>
+<pre><code>host    synapse     synapse_user    ::1/128     trust
+</code></pre>
+</li>
+</ul>
+<p>Note that line order matters in <code>pg_hba.conf</code>, so make sure that if you do add a
+new line, it is inserted before:</p>
+<pre><code>host    all         all             ::1/128     ident
+</code></pre>
+<h3 id="fixing-incorrect-collate-or-ctype"><a class="header" href="#fixing-incorrect-collate-or-ctype">Fixing incorrect <code>COLLATE</code> or <code>CTYPE</code></a></h3>
+<p>Synapse will refuse to set up a new database if it has the wrong values of
+<code>COLLATE</code> and <code>CTYPE</code> set. Synapse will also refuse to start an existing database with incorrect values
+of <code>COLLATE</code> and <code>CTYPE</code> unless the config flag <code>allow_unsafe_locale</code>, found in the 
+<code>database</code> section of the config, is set to true. Using different locales can cause issues if the locale library is updated from
+underneath the database, or if a different version of the locale is used on any
+replicas.</p>
+<p>If you have a databse with an unsafe locale, the safest way to fix the issue is to dump the database and recreate it with
+the correct locale parameter (as shown above). It is also possible to change the
+parameters on a live database and run a <code>REINDEX</code> on the entire database,
+however extreme care must be taken to avoid database corruption.</p>
+<p>Note that the above may fail with an error about duplicate rows if corruption
+has already occurred, and such duplicate rows will need to be manually removed.</p>
+<h3 id="fixing-inconsistent-sequences-error"><a class="header" href="#fixing-inconsistent-sequences-error">Fixing inconsistent sequences error</a></h3>
+<p>Synapse uses Postgres sequences to generate IDs for various tables. A sequence
+and associated table can get out of sync if, for example, Synapse has been
+downgraded and then upgraded again.</p>
+<p>To fix the issue shut down Synapse (including any and all workers) and run the
+SQL command included in the error message. Once done Synapse should start
+successfully.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="using-a-reverse-proxy-with-synapse"><a class="header" href="#using-a-reverse-proxy-with-synapse">Using a reverse proxy with Synapse</a></h1>
+<p>It is recommended to put a reverse proxy such as
+<a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html">nginx</a>,
+<a href="https://httpd.apache.org/docs/current/mod/mod_proxy_http.html">Apache</a>,
+<a href="https://caddyserver.com/docs/quick-starts/reverse-proxy">Caddy</a>,
+<a href="https://www.haproxy.org/">HAProxy</a> or
+<a href="https://man.openbsd.org/relayd.8">relayd</a> in front of Synapse. One advantage
+of doing so is that it means that you can expose the default https port
+(443) to Matrix clients without needing to run Synapse with root
+privileges.</p>
+<p>You should configure your reverse proxy to forward requests to <code>/_matrix</code> or
+<code>/_synapse/client</code> to Synapse, and have it set the <code>X-Forwarded-For</code> and
+<code>X-Forwarded-Proto</code> request headers.</p>
+<p>You should remember that Matrix clients and other Matrix servers do not
+necessarily need to connect to your server via the same server name or
+port. Indeed, clients will use port 443 by default, whereas servers default to
+port 8448. Where these are different, we refer to the 'client port' and the
+'federation port'. See <a href="https://matrix.org/docs/spec/server_server/latest#resolving-server-names">the Matrix
+specification</a>
+for more details of the algorithm used for federation connections, and
+<a href="delegate.html">Delegation</a> for instructions on setting up delegation.</p>
+<p><strong>NOTE</strong>: Your reverse proxy must not <code>canonicalise</code> or <code>normalise</code>
+the requested URI in any way (for example, by decoding <code>%xx</code> escapes).
+Beware that Apache <em>will</em> canonicalise URIs unless you specify
+<code>nocanon</code>.</p>
+<p>Let's assume that we expect clients to connect to our server at
+<code>https://matrix.example.com</code>, and other servers to connect at
+<code>https://example.com:8448</code>.  The following sections detail the configuration of
+the reverse proxy and the homeserver.</p>
+<h2 id="homeserver-configuration"><a class="header" href="#homeserver-configuration">Homeserver Configuration</a></h2>
+<p>The HTTP configuration will need to be updated for Synapse to correctly record 
+client IP addresses and generate redirect URLs while behind a reverse proxy. </p>
+<p>In <code>homeserver.yaml</code> set <code>x_forwarded: true</code> in the port 8008 section and 
+consider setting <code>bind_addresses: ['127.0.0.1']</code> so that the server only
+listens to traffic on localhost. (Do not change <code>bind_addresses</code> to <code>127.0.0.1</code> 
+when using a containerized Synapse, as that will prevent it from responding
+to proxied traffic.)</p>
+<h2 id="reverse-proxy-configuration-examples"><a class="header" href="#reverse-proxy-configuration-examples">Reverse-proxy configuration examples</a></h2>
+<p><strong>NOTE</strong>: You only need one of these.</p>
+<h3 id="nginx"><a class="header" href="#nginx">nginx</a></h3>
+<pre><code class="language-nginx">server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+
+    # For the federation port
+    listen 8448 ssl http2 default_server;
+    listen [::]:8448 ssl http2 default_server;
+
+    server_name matrix.example.com;
+
+    location ~ ^(/_matrix|/_synapse/client) {
+        # note: do not add a path (even a single /) after the port in `proxy_pass`,
+        # otherwise nginx will canonicalise the URI and cause signature verification
+        # errors.
+        proxy_pass http://localhost:8008;
+        proxy_set_header X-Forwarded-For $remote_addr;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Host $host;
+
+        # Nginx by default only allows file uploads up to 1M in size
+        # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
+        client_max_body_size 50M;
+    }
+}
+</code></pre>
+<h3 id="caddy-v1"><a class="header" href="#caddy-v1">Caddy v1</a></h3>
+<pre><code>matrix.example.com {
+  proxy /_matrix http://localhost:8008 {
+    transparent
+  }
+
+  proxy /_synapse/client http://localhost:8008 {
+    transparent
+  }
+}
+
+example.com:8448 {
+  proxy / http://localhost:8008 {
+    transparent
+  }
+}
+</code></pre>
+<h3 id="caddy-v2"><a class="header" href="#caddy-v2">Caddy v2</a></h3>
+<pre><code>matrix.example.com {
+  reverse_proxy /_matrix/* http://localhost:8008
+  reverse_proxy /_synapse/client/* http://localhost:8008
+}
+
+example.com:8448 {
+  reverse_proxy http://localhost:8008
+}
+</code></pre>
+<p><a href="delegate.html">Delegation</a> example:</p>
+<pre><code>(matrix-well-known-header) {
+    # Headers
+    header Access-Control-Allow-Origin &quot;*&quot;
+    header Access-Control-Allow-Methods &quot;GET, POST, PUT, DELETE, OPTIONS&quot;
+    header Access-Control-Allow-Headers &quot;Origin, X-Requested-With, Content-Type, Accept, Authorization&quot;
+    header Content-Type &quot;application/json&quot;
+}
+
+example.com {
+    handle /.well-known/matrix/server {
+        import matrix-well-known-header
+        respond `{&quot;m.server&quot;:&quot;matrix.example.com:443&quot;}`
+    }
+
+    handle /.well-known/matrix/client {
+        import matrix-well-known-header
+        respond `{&quot;m.homeserver&quot;:{&quot;base_url&quot;:&quot;https://matrix.example.com&quot;},&quot;m.identity_server&quot;:{&quot;base_url&quot;:&quot;https://identity.example.com&quot;}}`
+    }
+}
+
+matrix.example.com {
+    reverse_proxy /_matrix/* http://localhost:8008
+    reverse_proxy /_synapse/client/* http://localhost:8008
+}
+</code></pre>
+<h3 id="apache"><a class="header" href="#apache">Apache</a></h3>
+<pre><code class="language-apache">&lt;VirtualHost *:443&gt;
+    SSLEngine on
+    ServerName matrix.example.com
+
+    RequestHeader set &quot;X-Forwarded-Proto&quot; expr=%{REQUEST_SCHEME}
+    AllowEncodedSlashes NoDecode
+    ProxyPreserveHost on
+    ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
+    ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
+    ProxyPass /_synapse/client http://127.0.0.1:8008/_synapse/client nocanon
+    ProxyPassReverse /_synapse/client http://127.0.0.1:8008/_synapse/client
+&lt;/VirtualHost&gt;
+
+&lt;VirtualHost *:8448&gt;
+    SSLEngine on
+    ServerName example.com
+
+    RequestHeader set &quot;X-Forwarded-Proto&quot; expr=%{REQUEST_SCHEME}
+    AllowEncodedSlashes NoDecode
+    ProxyPass /_matrix http://127.0.0.1:8008/_matrix nocanon
+    ProxyPassReverse /_matrix http://127.0.0.1:8008/_matrix
+&lt;/VirtualHost&gt;
+</code></pre>
+<p><strong>NOTE</strong>: ensure the  <code>nocanon</code> options are included.</p>
+<p><strong>NOTE 2</strong>: It appears that Synapse is currently incompatible with the ModSecurity module for Apache (<code>mod_security2</code>). If you need it enabled for other services on your web server, you can disable it for Synapse's two VirtualHosts by including the following lines before each of the two <code>&lt;/VirtualHost&gt;</code> above:</p>
+<pre><code class="language-apache">&lt;IfModule security2_module&gt;
+    SecRuleEngine off
+&lt;/IfModule&gt;
+</code></pre>
+<p><strong>NOTE 3</strong>: Missing <code>ProxyPreserveHost on</code> can lead to a redirect loop.</p>
+<h3 id="haproxy"><a class="header" href="#haproxy">HAProxy</a></h3>
+<pre><code>frontend https
+  bind *:443,[::]:443 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
+  http-request set-header X-Forwarded-Proto https if { ssl_fc }
+  http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
+  http-request set-header X-Forwarded-For %[src]
+
+  # Matrix client traffic
+  acl matrix-host hdr(host) -i matrix.example.com matrix.example.com:443
+  acl matrix-path path_beg /_matrix
+  acl matrix-path path_beg /_synapse/client
+
+  use_backend matrix if matrix-host matrix-path
+
+frontend matrix-federation
+  bind *:8448,[::]:8448 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
+  http-request set-header X-Forwarded-Proto https if { ssl_fc }
+  http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
+  http-request set-header X-Forwarded-For %[src]
+
+  default_backend matrix
+
+backend matrix
+  server matrix 127.0.0.1:8008
+</code></pre>
+<h3 id="relayd"><a class="header" href="#relayd">Relayd</a></h3>
+<pre><code>table &lt;webserver&gt;    { 127.0.0.1 }
+table &lt;matrixserver&gt; { 127.0.0.1 }
+
+http protocol &quot;https&quot; {
+    tls { no tlsv1.0, ciphers &quot;HIGH&quot; }
+    tls keypair &quot;example.com&quot;
+    match header set &quot;X-Forwarded-For&quot;   value &quot;$REMOTE_ADDR&quot;
+    match header set &quot;X-Forwarded-Proto&quot; value &quot;https&quot;
+
+    # set CORS header for .well-known/matrix/server, .well-known/matrix/client
+    # httpd does not support setting headers, so do it here
+    match request path &quot;/.well-known/matrix/*&quot; tag &quot;matrix-cors&quot;
+    match response tagged &quot;matrix-cors&quot; header set &quot;Access-Control-Allow-Origin&quot; value &quot;*&quot;
+
+    pass quick path &quot;/_matrix/*&quot;         forward to &lt;matrixserver&gt;
+    pass quick path &quot;/_synapse/client/*&quot; forward to &lt;matrixserver&gt;
+
+    # pass on non-matrix traffic to webserver
+    pass                                 forward to &lt;webserver&gt;
+}
+
+relay &quot;https_traffic&quot; {
+    listen on egress port 443 tls
+    protocol &quot;https&quot;
+    forward to &lt;matrixserver&gt; port 8008 check tcp
+    forward to &lt;webserver&gt;    port 8080 check tcp
+}
+
+http protocol &quot;matrix&quot; {
+    tls { no tlsv1.0, ciphers &quot;HIGH&quot; }
+    tls keypair &quot;example.com&quot;
+    block
+    pass quick path &quot;/_matrix/*&quot;         forward to &lt;matrixserver&gt;
+    pass quick path &quot;/_synapse/client/*&quot; forward to &lt;matrixserver&gt;
+}
+
+relay &quot;matrix_federation&quot; {
+    listen on egress port 8448 tls
+    protocol &quot;matrix&quot;
+    forward to &lt;matrixserver&gt; port 8008 check tcp
+}
+</code></pre>
+<h2 id="health-check-endpoint"><a class="header" href="#health-check-endpoint">Health check endpoint</a></h2>
+<p>Synapse exposes a health check endpoint for use by reverse proxies.
+Each configured HTTP listener has a <code>/health</code> endpoint which always returns
+200 OK (and doesn't get logged).</p>
+<h2 id="synapse-administration-endpoints"><a class="header" href="#synapse-administration-endpoints">Synapse administration endpoints</a></h2>
+<p>Endpoints for administering your Synapse instance are placed under
+<code>/_synapse/admin</code>. These require authentication through an access token of an
+admin user. However as access to these endpoints grants the caller a lot of power,
+we do not recommend exposing them to the public internet without good reason.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="using-a-forward-proxy-with-synapse"><a class="header" href="#using-a-forward-proxy-with-synapse">Using a forward proxy with Synapse</a></h1>
+<p>You can use Synapse with a forward or outbound proxy. An example of when
+this is necessary is in corporate environments behind a DMZ (demilitarized zone).
+Synapse supports routing outbound HTTP(S) requests via a proxy. Only HTTP(S)
+proxy is supported, not SOCKS proxy or anything else.</p>
+<h2 id="configure"><a class="header" href="#configure">Configure</a></h2>
+<p>The <code>http_proxy</code>, <code>https_proxy</code>, <code>no_proxy</code> environment variables are used to
+specify proxy settings. The environment variable is not case sensitive.</p>
+<ul>
+<li><code>http_proxy</code>: Proxy server to use for HTTP requests.</li>
+<li><code>https_proxy</code>: Proxy server to use for HTTPS requests.</li>
+<li><code>no_proxy</code>: Comma-separated list of hosts, IP addresses, or IP ranges in CIDR
+format which should not use the proxy. Synapse will directly connect to these hosts.</li>
+</ul>
+<p>The <code>http_proxy</code> and <code>https_proxy</code> environment variables have the form: <code>[scheme://][&lt;username&gt;:&lt;password&gt;@]&lt;host&gt;[:&lt;port&gt;]</code></p>
+<ul>
+<li>
+<p>Supported schemes are <code>http://</code> and <code>https://</code>. The default scheme is <code>http://</code>
+for compatibility reasons; it is recommended to set a scheme. If scheme is set
+to <code>https://</code> the connection uses TLS between Synapse and the proxy.</p>
+<p><strong>NOTE</strong>: Synapse validates the certificates. If the certificate is not
+valid, then the connection is dropped.</p>
+</li>
+<li>
+<p>Default port if not given is <code>1080</code>.</p>
+</li>
+<li>
+<p>Username and password are optional and will be used to authenticate against
+the proxy.</p>
+</li>
+</ul>
+<p><strong>Examples</strong></p>
+<ul>
+<li>HTTP_PROXY=http://USERNAME:PASSWORD@10.0.1.1:8080/</li>
+<li>HTTPS_PROXY=http://USERNAME:PASSWORD@proxy.example.com:8080/</li>
+<li>NO_PROXY=master.hostname.example.com,10.1.0.0/16,172.30.0.0/16</li>
+</ul>
+<p><strong>NOTE</strong>:
+Synapse does not apply the IP blacklist to connections through the proxy (since
+the DNS resolution is done by the proxy). It is expected that the proxy or firewall
+will apply blacklisting of IP addresses.</p>
+<h2 id="connection-types"><a class="header" href="#connection-types">Connection types</a></h2>
+<p>The proxy will be <strong>used</strong> for:</p>
+<ul>
+<li>push</li>
+<li>url previews</li>
+<li>phone-home stats</li>
+<li>recaptcha validation</li>
+<li>CAS auth validation</li>
+<li>OpenID Connect</li>
+<li>Outbound federation</li>
+<li>Federation (checking public key revocation)</li>
+<li>Fetching public keys of other servers</li>
+<li>Downloading remote media</li>
+</ul>
+<p>It will <strong>not be used</strong> for:</p>
+<ul>
+<li>Application Services</li>
+<li>Identity servers</li>
+<li>In worker configurations
+<ul>
+<li>connections between workers</li>
+<li>connections from workers to Redis</li>
+</ul>
+</li>
+</ul>
+<h2 id="troubleshooting-1"><a class="header" href="#troubleshooting-1">Troubleshooting</a></h2>
+<p>If a proxy server is used with TLS (HTTPS) and no connections are established,
+it is most likely due to the proxy's certificates. To test this, the validation
+in Synapse can be deactivated.</p>
+<p><strong>NOTE</strong>: This has an impact on security and is for testing purposes only!</p>
+<p>To deactivate the certificate validation, the following setting must be made in
+<a href="setup/../usage/configuration/homeserver_sample_config.html">homserver.yaml</a>.</p>
+<pre><code class="language-yaml">use_insecure_ssl_client_just_for_testing_do_not_use: true
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="overview-1"><a class="header" href="#overview-1">Overview</a></h1>
+<p>This document explains how to enable VoIP relaying on your homeserver with
+TURN.</p>
+<p>The synapse Matrix homeserver supports integration with TURN server via the
+<a href="https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00">TURN server REST API</a>. This
+allows the homeserver to generate credentials that are valid for use on the
+TURN server through the use of a secret shared between the homeserver and the
+TURN server.</p>
+<p>The following sections describe how to install <a href="https://github.com/coturn/coturn">coturn</a> (which implements the TURN REST API) and integrate it with synapse.</p>
+<h2 id="requirements"><a class="header" href="#requirements">Requirements</a></h2>
+<p>For TURN relaying with <code>coturn</code> to work, it must be hosted on a server/endpoint with a public IP.</p>
+<p>Hosting TURN behind NAT requires port forwaring and for the NAT gateway to have a public IP.
+However, even with appropriate configuration, NAT is known to cause issues and to often not work.</p>
+<h2 id="coturn-setup"><a class="header" href="#coturn-setup"><code>coturn</code> setup</a></h2>
+<h3 id="initial-installation"><a class="header" href="#initial-installation">Initial installation</a></h3>
+<p>The TURN daemon <code>coturn</code> is available from a variety of sources such as native package managers, or installation from source.</p>
+<h4 id="debian-installation"><a class="header" href="#debian-installation">Debian installation</a></h4>
+<p>Just install the debian package:</p>
+<pre><code class="language-sh">apt install coturn
+</code></pre>
+<p>This will install and start a systemd service called <code>coturn</code>.</p>
+<h4 id="source-installation"><a class="header" href="#source-installation">Source installation</a></h4>
+<ol>
+<li>
+<p>Download the <a href="https://github.com/coturn/coturn/releases/latest">latest release</a> from github.  Unpack it and <code>cd</code> into the directory.</p>
+</li>
+<li>
+<p>Configure it:</p>
+<pre><code class="language-sh">./configure
+</code></pre>
+<p>You may need to install <code>libevent2</code>: if so, you should do so in
+the way recommended by your operating system. You can ignore
+warnings about lack of database support: a database is unnecessary
+for this purpose.</p>
+</li>
+<li>
+<p>Build and install it:</p>
+<pre><code class="language-sh">make
+make install
+</code></pre>
+</li>
+</ol>
+<h3 id="configuration"><a class="header" href="#configuration">Configuration</a></h3>
+<ol>
+<li>
+<p>Create or edit the config file in <code>/etc/turnserver.conf</code>. The relevant
+lines, with example values, are:</p>
+<pre><code>use-auth-secret
+static-auth-secret=[your secret key here]
+realm=turn.myserver.org
+</code></pre>
+<p>See <code>turnserver.conf</code> for explanations of the options. One way to generate
+the <code>static-auth-secret</code> is with <code>pwgen</code>:</p>
+<pre><code class="language-sh">pwgen -s 64 1
+</code></pre>
+<p>A <code>realm</code> must be specified, but its value is somewhat arbitrary. (It is
+sent to clients as part of the authentication flow.) It is conventional to
+set it to be your server name.</p>
+</li>
+<li>
+<p>You will most likely want to configure coturn to write logs somewhere. The
+easiest way is normally to send them to the syslog:</p>
+<pre><code class="language-sh">syslog
+</code></pre>
+<p>(in which case, the logs will be available via <code>journalctl -u coturn</code> on a
+systemd system). Alternatively, coturn can be configured to write to a
+logfile - check the example config file supplied with coturn.</p>
+</li>
+<li>
+<p>Consider your security settings. TURN lets users request a relay which will
+connect to arbitrary IP addresses and ports. The following configuration is
+suggested as a minimum starting point:</p>
+<pre><code># VoIP traffic is all UDP. There is no reason to let users connect to arbitrary TCP endpoints via the relay.
+no-tcp-relay
+
+# don't let the relay ever try to connect to private IP address ranges within your network (if any)
+# given the turn server is likely behind your firewall, remember to include any privileged public IPs too.
+denied-peer-ip=10.0.0.0-10.255.255.255
+denied-peer-ip=192.168.0.0-192.168.255.255
+denied-peer-ip=172.16.0.0-172.31.255.255
+
+# recommended additional local peers to block, to mitigate external access to internal services.
+# https://www.rtcsec.com/article/slack-webrtc-turn-compromise-and-bug-bounty/#how-to-fix-an-open-turn-relay-to-address-this-vulnerability
+no-multicast-peers
+denied-peer-ip=0.0.0.0-0.255.255.255
+denied-peer-ip=100.64.0.0-100.127.255.255
+denied-peer-ip=127.0.0.0-127.255.255.255
+denied-peer-ip=169.254.0.0-169.254.255.255
+denied-peer-ip=192.0.0.0-192.0.0.255
+denied-peer-ip=192.0.2.0-192.0.2.255
+denied-peer-ip=192.88.99.0-192.88.99.255
+denied-peer-ip=198.18.0.0-198.19.255.255
+denied-peer-ip=198.51.100.0-198.51.100.255
+denied-peer-ip=203.0.113.0-203.0.113.255
+denied-peer-ip=240.0.0.0-255.255.255.255
+
+# special case the turn server itself so that client-&gt;TURN-&gt;TURN-&gt;client flows work
+# this should be one of the turn server's listening IPs
+allowed-peer-ip=10.0.0.1
+
+# consider whether you want to limit the quota of relayed streams per user (or total) to avoid risk of DoS.
+user-quota=12 # 4 streams per video call, so 12 streams = 3 simultaneous relayed calls per user.
+total-quota=1200
+</code></pre>
+</li>
+<li>
+<p>Also consider supporting TLS/DTLS. To do this, add the following settings
+to <code>turnserver.conf</code>:</p>
+<pre><code># TLS certificates, including intermediate certs.
+# For Let's Encrypt certificates, use `fullchain.pem` here.
+cert=/path/to/fullchain.pem
+
+# TLS private key file
+pkey=/path/to/privkey.pem
+
+# Ensure the configuration lines that disable TLS/DTLS are commented-out or removed
+#no-tls
+#no-dtls
+</code></pre>
+<p>In this case, replace the <code>turn:</code> schemes in the <code>turn_uris</code> settings below
+with <code>turns:</code>.</p>
+<p>We recommend that you only try to set up TLS/DTLS once you have set up a
+basic installation and got it working.</p>
+<p>NB: If your TLS certificate was provided by Let's Encrypt, TLS/DTLS will
+not work with any Matrix client that uses Chromium's WebRTC library. This
+currently includes Element Android &amp; iOS; for more details, see their
+<a href="https://github.com/vector-im/element-android/issues/1533">respective</a>
+<a href="https://github.com/vector-im/element-ios/issues/2712">issues</a> as well as the underlying
+<a href="https://bugs.chromium.org/p/webrtc/issues/detail?id=11710">WebRTC issue</a>.
+Consider using a ZeroSSL certificate for your TURN server as a working alternative.</p>
+</li>
+<li>
+<p>Ensure your firewall allows traffic into the TURN server on the ports
+you've configured it to listen on (By default: 3478 and 5349 for TURN
+traffic (remember to allow both TCP and UDP traffic), and ports 49152-65535
+for the UDP relay.)</p>
+</li>
+<li>
+<p>If your TURN server is behind NAT, the NAT gateway must have an external,
+publicly-reachable IP address. You must configure coturn to advertise that
+address to connecting clients:</p>
+<pre><code>external-ip=EXTERNAL_NAT_IPv4_ADDRESS
+</code></pre>
+<p>You may optionally limit the TURN server to listen only on the local
+address that is mapped by NAT to the external address:</p>
+<pre><code>listening-ip=INTERNAL_TURNSERVER_IPv4_ADDRESS
+</code></pre>
+<p>If your NAT gateway is reachable over both IPv4 and IPv6, you may
+configure coturn to advertise each available address:</p>
+<pre><code>external-ip=EXTERNAL_NAT_IPv4_ADDRESS
+external-ip=EXTERNAL_NAT_IPv6_ADDRESS
+</code></pre>
+<p>When advertising an external IPv6 address, ensure that the firewall and
+network settings of the system running your TURN server are configured to
+accept IPv6 traffic, and that the TURN server is listening on the local
+IPv6 address that is mapped by NAT to the external IPv6 address.</p>
+</li>
+<li>
+<p>(Re)start the turn server:</p>
+<ul>
+<li>
+<p>If you used the Debian package (or have set up a systemd unit yourself):</p>
+<pre><code class="language-sh">systemctl restart coturn
+</code></pre>
+</li>
+<li>
+<p>If you installed from source:</p>
+<pre><code class="language-sh">bin/turnserver -o
+</code></pre>
+</li>
+</ul>
+</li>
+</ol>
+<h2 id="synapse-setup"><a class="header" href="#synapse-setup">Synapse setup</a></h2>
+<p>Your homeserver configuration file needs the following extra keys:</p>
+<ol>
+<li>&quot;<code>turn_uris</code>&quot;: This needs to be a yaml list of public-facing URIs
+for your TURN server to be given out to your clients. Add separate
+entries for each transport your TURN server supports.</li>
+<li>&quot;<code>turn_shared_secret</code>&quot;: This is the secret shared between your
+homeserver and your TURN server, so you should set it to the same
+string you used in turnserver.conf.</li>
+<li>&quot;<code>turn_user_lifetime</code>&quot;: This is the amount of time credentials
+generated by your homeserver are valid for (in milliseconds).
+Shorter times offer less potential for abuse at the expense of
+increased traffic between web clients and your homeserver to
+refresh credentials. The TURN REST API specification recommends
+one day (86400000).</li>
+<li>&quot;<code>turn_allow_guests</code>&quot;: Whether to allow guest users to use the
+TURN server. This is enabled by default, as otherwise VoIP will
+not work reliably for guests. However, it does introduce a
+security risk as it lets guests connect to arbitrary endpoints
+without having gone through a CAPTCHA or similar to register a
+real account.</li>
+</ol>
+<p>As an example, here is the relevant section of the config file for <code>matrix.org</code>. The
+<code>turn_uris</code> are appropriate for TURN servers listening on the default ports, with no TLS.</p>
+<pre><code>turn_uris: [ &quot;turn:turn.matrix.org?transport=udp&quot;, &quot;turn:turn.matrix.org?transport=tcp&quot; ]
+turn_shared_secret: &quot;n0t4ctuAllymatr1Xd0TorgSshar3d5ecret4obvIousreAsons&quot;
+turn_user_lifetime: 86400000
+turn_allow_guests: True
+</code></pre>
+<p>After updating the homeserver configuration, you must restart synapse:</p>
+<ul>
+<li>If you use synctl:
+<pre><code class="language-sh"># Depending on how Synapse is installed, synctl may already be on
+# your PATH. If not, you may need to activate a virtual environment.
+synctl restart
+</code></pre>
+</li>
+<li>If you use systemd:
+<pre><code class="language-sh">systemctl restart matrix-synapse.service
+</code></pre>
+</li>
+</ul>
+<p>... and then reload any clients (or wait an hour for them to refresh their
+settings).</p>
+<h2 id="troubleshooting-2"><a class="header" href="#troubleshooting-2">Troubleshooting</a></h2>
+<p>The normal symptoms of a misconfigured TURN server are that calls between
+devices on different networks ring, but get stuck at &quot;call
+connecting&quot;. Unfortunately, troubleshooting this can be tricky.</p>
+<p>Here are a few things to try:</p>
+<ul>
+<li>
+<p>Check that you have opened your firewall to allow TCP and UDP traffic to the
+TURN ports (normally 3478 and 5349).</p>
+</li>
+<li>
+<p>Check that you have opened your firewall to allow UDP traffic to the UDP
+relay ports (49152-65535 by default).</p>
+</li>
+<li>
+<p>Try disabling <code>coturn</code>'s TLS/DTLS listeners and enable only its (unencrypted)
+TCP/UDP listeners. (This will only leave signaling traffic unencrypted;
+voice &amp; video WebRTC traffic is always encrypted.)</p>
+</li>
+<li>
+<p>Some WebRTC implementations (notably, that of Google Chrome) appear to get
+confused by TURN servers which are reachable over IPv6 (this appears to be
+an unexpected side-effect of its handling of multiple IP addresses as
+defined by
+<a href="https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-12"><code>draft-ietf-rtcweb-ip-handling</code></a>).</p>
+<p>Try removing any AAAA records for your TURN server, so that it is only
+reachable over IPv4.</p>
+</li>
+<li>
+<p>If your TURN server is behind NAT:</p>
+<ul>
+<li>
+<p>double-check that your NAT gateway is correctly forwarding all TURN
+ports (normally 3478 &amp; 5349 for TCP &amp; UDP TURN traffic, and 49152-65535 for the UDP
+relay) to the NAT-internal address of your TURN server. If advertising
+both IPv4 and IPv6 external addresses via the <code>external-ip</code> option, ensure
+that the NAT is forwarding both IPv4 and IPv6 traffic to the IPv4 and IPv6
+internal addresses of your TURN server. When in doubt, remove AAAA records
+for your TURN server and specify only an IPv4 address as your <code>external-ip</code>.</p>
+</li>
+<li>
+<p>ensure that your TURN server uses the NAT gateway as its default route.</p>
+</li>
+</ul>
+</li>
+<li>
+<p>Enable more verbose logging in coturn via the <code>verbose</code> setting:</p>
+<pre><code>verbose
+</code></pre>
+<p>... and then see if there are any clues in its logs.</p>
+</li>
+<li>
+<p>If you are using a browser-based client under Chrome, check
+<code>chrome://webrtc-internals/</code> for insights into the internals of the
+negotiation. On Firefox, check the &quot;Connection Log&quot; on <code>about:webrtc</code>.</p>
+<p>(Understanding the output is beyond the scope of this document!)</p>
+</li>
+<li>
+<p>You can test your Matrix homeserver TURN setup with https://test.voip.librepush.net/.
+Note that this test is not fully reliable yet, so don't be discouraged if
+the test fails.
+<a href="https://github.com/matrix-org/voip-tester">Here</a> is the github repo of the
+source of the tester, where you can file bug reports.</p>
+</li>
+<li>
+<p>There is a WebRTC test tool at
+https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/. To
+use it, you will need a username/password for your TURN server. You can
+either:</p>
+<ul>
+<li>
+<p>look for the <code>GET /_matrix/client/r0/voip/turnServer</code> request made by a
+matrix client to your homeserver in your browser's network inspector. In
+the response you should see <code>username</code> and <code>password</code>. Or:</p>
+</li>
+<li>
+<p>Use the following shell commands:</p>
+<pre><code class="language-sh">secret=staticAuthSecretHere
+
+u=$((`date +%s` + 3600)):test
+p=$(echo -n $u | openssl dgst -hmac $secret -sha1 -binary | base64)
+echo -e &quot;username: $u\npassword: $p&quot;
+</code></pre>
+<p>Or:</p>
+</li>
+<li>
+<p>Temporarily configure coturn to accept a static username/password. To do
+this, comment out <code>use-auth-secret</code> and <code>static-auth-secret</code> and add the
+following:</p>
+<pre><code>lt-cred-mech
+user=username:password
+</code></pre>
+<p><strong>Note</strong>: these settings will not take effect unless <code>use-auth-secret</code>
+and <code>static-auth-secret</code> are disabled.</p>
+<p>Restart coturn after changing the configuration file.</p>
+<p>Remember to restore the original settings to go back to testing with
+Matrix clients!</p>
+</li>
+</ul>
+<p>If the TURN server is working correctly, you should see at least one <code>relay</code>
+entry in the results.</p>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="delegation-of-incoming-federation-traffic"><a class="header" href="#delegation-of-incoming-federation-traffic">Delegation of incoming federation traffic</a></h1>
+<p>In the following documentation, we use the term <code>server_name</code> to refer to that setting
+in your homeserver configuration file. It appears at the ends of user ids, and tells
+other homeservers where they can find your server.</p>
+<p>By default, other homeservers will expect to be able to reach yours via
+your <code>server_name</code>, on port 8448. For example, if you set your <code>server_name</code>
+to <code>example.com</code> (so that your user names look like <code>@user:example.com</code>),
+other servers will try to connect to yours at <code>https://example.com:8448/</code>.</p>
+<p>Delegation is a Matrix feature allowing a homeserver admin to retain a
+<code>server_name</code> of <code>example.com</code> so that user IDs, room aliases, etc continue
+to look like <code>*:example.com</code>, whilst having federation traffic routed
+to a different server and/or port (e.g. <code>synapse.example.com:443</code>).</p>
+<h2 id="well-known-delegation"><a class="header" href="#well-known-delegation">.well-known delegation</a></h2>
+<p>To use this method, you need to be able to configure the server at
+<code>https://&lt;server_name&gt;</code> to serve a file at
+<code>https://&lt;server_name&gt;/.well-known/matrix/server</code>.  There are two ways to do this, shown below.</p>
+<p>Note that the <code>.well-known</code> file is hosted on the default port for <code>https</code> (port 443).</p>
+<h3 id="external-server"><a class="header" href="#external-server">External server</a></h3>
+<p>For maximum flexibility, you need to configure an external server such as nginx, Apache
+or HAProxy to serve the <code>https://&lt;server_name&gt;/.well-known/matrix/server</code> file. Setting
+up such a server is out of the scope of this documentation, but note that it is often
+possible to configure your <a href="reverse_proxy.html">reverse proxy</a> for this.</p>
+<p>The URL <code>https://&lt;server_name&gt;/.well-known/matrix/server</code> should be configured
+return a JSON structure containing the key <code>m.server</code> like this:</p>
+<pre><code class="language-json">{
+    &quot;m.server&quot;: &quot;&lt;synapse.server.name&gt;[:&lt;yourport&gt;]&quot;
+}
+</code></pre>
+<p>In our example (where we want federation traffic to be routed to
+<code>https://synapse.example.com</code>, on port 443), this would mean that
+<code>https://example.com/.well-known/matrix/server</code> should return:</p>
+<pre><code class="language-json">{
+    &quot;m.server&quot;: &quot;synapse.example.com:443&quot;
+}
+</code></pre>
+<p>Note, specifying a port is optional. If no port is specified, then it defaults
+to 8448.</p>
+<h3 id="serving-a-well-knownmatrixserver-file-with-synapse"><a class="header" href="#serving-a-well-knownmatrixserver-file-with-synapse">Serving a <code>.well-known/matrix/server</code> file with Synapse</a></h3>
+<p>If you are able to set up your domain so that <code>https://&lt;server_name&gt;</code> is routed to
+Synapse (i.e., the only change needed is to direct federation traffic to port 443
+instead of port 8448), then it is possible to configure Synapse to serve a suitable
+<code>.well-known/matrix/server</code> file. To do so, add the following to your <code>homeserver.yaml</code>
+file:</p>
+<pre><code class="language-yaml">serve_server_wellknown: true
+</code></pre>
+<p><strong>Note</strong>: this <em>only</em> works if <code>https://&lt;server_name&gt;</code> is routed to Synapse, so is
+generally not suitable if Synapse is hosted at a subdomain such as
+<code>https://synapse.example.com</code>.</p>
+<h2 id="srv-dns-record-delegation"><a class="header" href="#srv-dns-record-delegation">SRV DNS record delegation</a></h2>
+<p>It is also possible to do delegation using a SRV DNS record. However, that is generally
+not recommended, as it can be difficult to configure the TLS certificates correctly in
+this case, and it offers little advantage over <code>.well-known</code> delegation.</p>
+<p>However, if you really need it, you can find some documentation on what such a
+record should look like and how Synapse will use it in <a href="https://matrix.org/docs/spec/server_server/latest#resolving-server-names">the Matrix
+specification</a>.</p>
+<h2 id="delegation-faq"><a class="header" href="#delegation-faq">Delegation FAQ</a></h2>
+<h3 id="when-do-i-need-delegation"><a class="header" href="#when-do-i-need-delegation">When do I need delegation?</a></h3>
+<p>If your homeserver's APIs are accessible on the default federation port (8448)
+and the domain your <code>server_name</code> points to, you do not need any delegation.</p>
+<p>For instance, if you registered <code>example.com</code> and pointed its DNS A record at a
+fresh server, you could install Synapse on that host, giving it a <code>server_name</code>
+of <code>example.com</code>, and once a reverse proxy has been set up to proxy all requests
+sent to the port <code>8448</code> and serve TLS certificates for <code>example.com</code>, you
+wouldn't need any delegation set up.</p>
+<p><strong>However</strong>, if your homeserver's APIs aren't accessible on port 8448 and on the
+domain <code>server_name</code> points to, you will need to let other servers know how to
+find it using delegation.</p>
+<h3 id="should-i-use-a-reverse-proxy-for-federation-traffic"><a class="header" href="#should-i-use-a-reverse-proxy-for-federation-traffic">Should I use a reverse proxy for federation traffic?</a></h3>
+<p>Generally, using a reverse proxy for both the federation and client traffic is a good
+idea, since it saves handling TLS traffic in Synapse. See
+<a href="reverse_proxy.html">the reverse proxy documentation</a> for information on setting up a
+reverse proxy.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="upgrading-synapse"><a class="header" href="#upgrading-synapse">Upgrading Synapse</a></h1>
+<p>Before upgrading check if any special steps are required to upgrade from
+the version you currently have installed to the current version of
+Synapse. The extra instructions that may be required are listed later in
+this document.</p>
+<ul>
+<li>
+<p>Check that your versions of Python and PostgreSQL are still
+supported.</p>
+<p>Synapse follows upstream lifecycles for <a href="https://endoflife.date/python">Python</a> and
+<a href="https://endoflife.date/postgresql">PostgreSQL</a>, and removes support for versions
+which are no longer maintained.</p>
+<p>The website <a href="https://endoflife.date">https://endoflife.date</a> also offers convenient
+summaries.</p>
+</li>
+<li>
+<p>If Synapse was installed using <a href="setup/installation.html#prebuilt-packages">prebuilt
+packages</a>, you will need to follow the
+normal process for upgrading those packages.</p>
+</li>
+<li>
+<p>If Synapse was installed from source, then:</p>
+<ol>
+<li>
+<p>Activate the virtualenv before upgrading. For example, if
+Synapse is installed in a virtualenv in <code>~/synapse/env</code> then
+run:</p>
+<pre><code class="language-bash">source ~/synapse/env/bin/activate
+</code></pre>
+</li>
+<li>
+<p>If Synapse was installed using pip then upgrade to the latest
+version by running:</p>
+<pre><code class="language-bash">pip install --upgrade matrix-synapse
+</code></pre>
+<p>If Synapse was installed using git then upgrade to the latest
+version by running:</p>
+<pre><code class="language-bash">git pull
+pip install --upgrade .
+</code></pre>
+</li>
+<li>
+<p>Restart Synapse:</p>
+<pre><code class="language-bash">synctl restart
+</code></pre>
+</li>
+</ol>
+</li>
+</ul>
+<p>To check whether your update was successful, you can check the running
+server version with:</p>
+<pre><code class="language-bash"># you may need to replace 'localhost:8008' if synapse is not configured
+# to listen on port 8008.
+
+curl http://localhost:8008/_synapse/admin/v1/server_version
+</code></pre>
+<h2 id="rolling-back-to-older-versions"><a class="header" href="#rolling-back-to-older-versions">Rolling back to older versions</a></h2>
+<p>Rolling back to previous releases can be difficult, due to database
+schema changes between releases. Where we have been able to test the
+rollback process, this will be noted below.</p>
+<p>In general, you will need to undo any changes made during the upgrade
+process, for example:</p>
+<ul>
+<li>
+<p>pip:</p>
+<pre><code class="language-bash">source env/bin/activate
+# replace `1.3.0` accordingly:
+pip install matrix-synapse==1.3.0
+</code></pre>
+</li>
+<li>
+<p>Debian:</p>
+<pre><code class="language-bash"># replace `1.3.0` and `stretch` accordingly:
+wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
+dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
+</code></pre>
+</li>
+</ul>
+<h1 id="upgrading-to-v1560"><a class="header" href="#upgrading-to-v1560">Upgrading to v1.56.0</a></h1>
+<h2 id="groupscommunities-feature-has-been-deprecated"><a class="header" href="#groupscommunities-feature-has-been-deprecated">Groups/communities feature has been deprecated</a></h2>
+<p>The non-standard groups/communities feature in Synapse has been deprecated and will
+be disabled by default in Synapse v1.58.0.</p>
+<p>You can test disabling it by adding the following to your homeserver configuration:</p>
+<pre><code class="language-yaml">experimental_features:
+  groups_enabled: false
+</code></pre>
+<h2 id="change-in-behaviour-for-postgresql-databases-with-unsafe-locale"><a class="header" href="#change-in-behaviour-for-postgresql-databases-with-unsafe-locale">Change in behaviour for PostgreSQL databases with unsafe locale</a></h2>
+<p>Synapse now refuses to start when using PostgreSQL with non-<code>C</code> values for <code>COLLATE</code> and
+<code>CTYPE</code> unless the config flag <code>allow_unsafe_locale</code>, found in the database section of
+the configuration file, is set to <code>true</code>. See the <a href="https://matrix-org.github.io/synapse/latest/postgres.html#fixing-incorrect-collate-or-ctype">PostgreSQL documentation</a>
+for more information and instructions on how to fix a database with incorrect values.</p>
+<h1 id="upgrading-to-v1550"><a class="header" href="#upgrading-to-v1550">Upgrading to v1.55.0</a></h1>
+<h2 id="open-registration-without-verification-is-now-disabled-by-default"><a class="header" href="#open-registration-without-verification-is-now-disabled-by-default">Open registration without verification is now disabled by default</a></h2>
+<p>Synapse will refuse to start if registration is enabled without email, captcha, or token-based verification unless the new config 
+flag <code>enable_registration_without_verification</code> is set to &quot;true&quot;.</p>
+<h2 id="synctl-script-has-been-moved"><a class="header" href="#synctl-script-has-been-moved"><code>synctl</code> script has been moved</a></h2>
+<p>The <code>synctl</code> script
+<a href="https://github.com/matrix-org/synapse/pull/12140">has been made</a> an
+<a href="https://packaging.python.org/en/latest/specifications/entry-points/">entry point</a>
+and no longer exists at the root of Synapse's source tree. If you wish to use
+<code>synctl</code> to manage your homeserver, you should invoke <code>synctl</code> directly, e.g. 
+<code>synctl start</code> instead of <code>./synctl start</code> or <code>/path/to/synctl start</code>. </p>
+<p>You will need to ensure <code>synctl</code> is on your <code>PATH</code>.</p>
+<ul>
+<li>This is automatically the case when using
+<a href="https://packages.matrix.org/debian/">Debian packages</a> or
+<a href="https://hub.docker.com/r/matrixdotorg/synapse">docker images</a>
+provided by Matrix.org.</li>
+<li>When installing from a wheel, sdist, or PyPI, a <code>synctl</code> executable is added 
+to your Python installation's <code>bin</code>. This should be on your <code>PATH</code>
+automatically, though you might need to activate a virtual environment
+depending on how you installed Synapse.</li>
+</ul>
+<h2 id="compatibility-dropped-for-mjolnir-131-and-earlier"><a class="header" href="#compatibility-dropped-for-mjolnir-131-and-earlier">Compatibility dropped for Mjolnir 1.3.1 and earlier</a></h2>
+<p>Synapse v1.55.0 drops support for Mjolnir 1.3.1 and earlier.
+If you use the Mjolnir module to moderate your homeserver,
+please upgrade Mjolnir to version 1.3.2 or later before upgrading Synapse.</p>
+<h1 id="upgrading-to-v1540"><a class="header" href="#upgrading-to-v1540">Upgrading to v1.54.0</a></h1>
+<h2 id="legacy-structured-logging-configuration-removal"><a class="header" href="#legacy-structured-logging-configuration-removal">Legacy structured logging configuration removal</a></h2>
+<p>This release removes support for the <code>structured: true</code> logging configuration
+which was deprecated in Synapse v1.23.0. If your logging configuration contains
+<code>structured: true</code> then it should be modified based on the
+<a href="structured_logging.html">structured logging documentation</a>.</p>
+<h1 id="upgrading-to-v1530"><a class="header" href="#upgrading-to-v1530">Upgrading to v1.53.0</a></h1>
+<h2 id="dropping-support-for-webclient-listeners-and-non-https-web_client_location"><a class="header" href="#dropping-support-for-webclient-listeners-and-non-https-web_client_location">Dropping support for <code>webclient</code> listeners and non-HTTP(S) <code>web_client_location</code></a></h2>
+<p>Per the deprecation notice in Synapse v1.51.0, listeners of type  <code>webclient</code>
+are no longer supported and configuring them is a now a configuration error.</p>
+<p>Configuring a non-HTTP(S) <code>web_client_location</code> configuration is is now a
+configuration error. Since the <code>webclient</code> listener is no longer supported, this
+setting only applies to the root path <code>/</code> of Synapse's web server and no longer
+the <code>/_matrix/client/</code> path.</p>
+<h2 id="stablisation-of-msc3231"><a class="header" href="#stablisation-of-msc3231">Stablisation of MSC3231</a></h2>
+<p>The unstable validity-check endpoint for the 
+<a href="https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv1registermloginregistration_tokenvalidity">Registration Tokens</a> 
+feature has been stabilised and moved from:</p>
+<p><code>/_matrix/client/unstable/org.matrix.msc3231/register/org.matrix.msc3231.login.registration_token/validity</code></p>
+<p>to:</p>
+<p><code>/_matrix/client/v1/register/m.login.registration_token/validity</code></p>
+<p>Please update any relevant reverse proxy or firewall configurations appropriately.</p>
+<h2 id="time-based-cache-expiry-is-now-enabled-by-default"><a class="header" href="#time-based-cache-expiry-is-now-enabled-by-default">Time-based cache expiry is now enabled by default</a></h2>
+<p>Formerly, entries in the cache were not evicted regardless of whether they were accessed after storing.
+This behavior has now changed. By default entries in the cache are now evicted after 30m of not being accessed. 
+To change the default behavior, go to the <code>caches</code> section of the config and change the <code>expire_caches</code> and 
+<code>cache_entry_ttl</code> flags as necessary. Please note that these flags replace the <code>expiry_time</code> flag in the config.<br />
+The <code>expiry_time</code> flag will still continue to work, but it has been deprecated and will be removed in the future.</p>
+<h2 id="deprecation-of-capability-orgmatrixmsc3283"><a class="header" href="#deprecation-of-capability-orgmatrixmsc3283">Deprecation of <code>capability</code> <code>org.matrix.msc3283.*</code></a></h2>
+<p>The <code>capabilities</code> of MSC3283 from the REST API <code>/_matrix/client/r0/capabilities</code>
+becomes stable.</p>
+<p>The old <code>capabilities</code></p>
+<ul>
+<li><code>org.matrix.msc3283.set_displayname</code>,</li>
+<li><code>org.matrix.msc3283.set_avatar_url</code> and</li>
+<li><code>org.matrix.msc3283.3pid_changes</code></li>
+</ul>
+<p>are deprecated and scheduled to be removed in Synapse v1.54.0.</p>
+<p>The new <code>capabilities</code></p>
+<ul>
+<li><code>m.set_displayname</code>,</li>
+<li><code>m.set_avatar_url</code> and</li>
+<li><code>m.3pid_changes</code></li>
+</ul>
+<p>are now active by default.</p>
+<h2 id="removal-of-user_may_create_room_with_invites"><a class="header" href="#removal-of-user_may_create_room_with_invites">Removal of <code>user_may_create_room_with_invites</code></a></h2>
+<p>As announced with the release of <a href="upgrade.html#deprecation-of-the-user_may_create_room_with_invites-module-callback">Synapse 1.47.0</a>,
+the deprecated <code>user_may_create_room_with_invites</code> module callback has been removed.</p>
+<p>Modules relying on it can instead implement <a href="https://matrix-org.github.io/synapse/latest/modules/spam_checker_callbacks.html#user_may_invite"><code>user_may_invite</code></a>
+and use the <a href="https://github.com/matrix-org/synapse/blob/872f23b95fa980a61b0866c1475e84491991fa20/synapse/module_api/__init__.py#L869-L876"><code>get_room_state</code></a>
+module API to infer whether the invite is happening while creating a room (see <a href="https://github.com/matrix-org/synapse-domain-rule-checker/blob/e7d092dd9f2a7f844928771dbfd9fd24c2332e48/synapse_domain_rule_checker/__init__.py#L56-L89">this function</a>
+as an example). Alternately, modules can also implement <a href="https://matrix-org.github.io/synapse/latest/modules/third_party_rules_callbacks.html#on_create_room"><code>on_create_room</code></a>.</p>
+<h1 id="upgrading-to-v1520"><a class="header" href="#upgrading-to-v1520">Upgrading to v1.52.0</a></h1>
+<h2 id="twisted-security-release"><a class="header" href="#twisted-security-release">Twisted security release</a></h2>
+<p>Note that <a href="https://github.com/twisted/twisted/releases/tag/twisted-22.1.0">Twisted 22.1.0</a>
+has recently been released, which fixes a <a href="https://github.com/twisted/twisted/security/advisories/GHSA-92x2-jw7w-xvvx">security issue</a>
+within the Twisted library. We do not believe Synapse is affected by this vulnerability,
+though we advise server administrators who installed Synapse via pip to upgrade Twisted
+with <code>pip install --upgrade Twisted treq</code> as a matter of good practice. The Docker image
+<code>matrixdotorg/synapse</code> and the Debian packages from <code>packages.matrix.org</code> are using the
+updated library.</p>
+<h1 id="upgrading-to-v1510"><a class="header" href="#upgrading-to-v1510">Upgrading to v1.51.0</a></h1>
+<h2 id="deprecation-of-webclient-listeners-and-non-https-web_client_location"><a class="header" href="#deprecation-of-webclient-listeners-and-non-https-web_client_location">Deprecation of <code>webclient</code> listeners and non-HTTP(S) <code>web_client_location</code></a></h2>
+<p>Listeners of type  <code>webclient</code> are deprecated and scheduled to be removed in
+Synapse v1.53.0.</p>
+<p>Similarly, a non-HTTP(S) <code>web_client_location</code> configuration is deprecated and
+will become a configuration error in Synapse v1.53.0.</p>
+<h1 id="upgrading-to-v1500"><a class="header" href="#upgrading-to-v1500">Upgrading to v1.50.0</a></h1>
+<h2 id="dropping-support-for-old-python-and-postgres-versions"><a class="header" href="#dropping-support-for-old-python-and-postgres-versions">Dropping support for old Python and Postgres versions</a></h2>
+<p>In line with our <a href="deprecation_policy.html">deprecation policy</a>,
+we've dropped support for Python 3.6 and PostgreSQL 9.6, as they are no
+longer supported upstream.</p>
+<p>This release of Synapse requires Python 3.7+ and PostgreSQL 10+.</p>
+<h1 id="upgrading-to-v1470"><a class="header" href="#upgrading-to-v1470">Upgrading to v1.47.0</a></h1>
+<h2 id="removal-of-old-room-admin-api"><a class="header" href="#removal-of-old-room-admin-api">Removal of old Room Admin API</a></h2>
+<p>The following admin APIs were deprecated in <a href="https://github.com/matrix-org/synapse/blob/v1.34.0/CHANGES.md#deprecations-and-removals">Synapse 1.34</a>
+(released on 2021-05-17) and have now been removed:</p>
+<ul>
+<li><code>POST /_synapse/admin/v1/&lt;room_id&gt;/delete</code></li>
+</ul>
+<p>Any scripts still using the above APIs should be converted to use the
+<a href="https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-room-api">Delete Room API</a>.</p>
+<h2 id="deprecation-of-the-user_may_create_room_with_invites-module-callback"><a class="header" href="#deprecation-of-the-user_may_create_room_with_invites-module-callback">Deprecation of the <code>user_may_create_room_with_invites</code> module callback</a></h2>
+<p>The <code>user_may_create_room_with_invites</code> is deprecated and will be removed in a future
+version of Synapse. Modules implementing this callback can instead implement
+<a href="https://matrix-org.github.io/synapse/latest/modules/spam_checker_callbacks.html#user_may_invite"><code>user_may_invite</code></a>
+and use the <a href="https://github.com/matrix-org/synapse/blob/872f23b95fa980a61b0866c1475e84491991fa20/synapse/module_api/__init__.py#L869-L876"><code>get_room_state</code></a>
+module API method to infer whether the invite is happening in the context of creating a
+room.</p>
+<p>We plan to remove this callback in January 2022.</p>
+<h1 id="upgrading-to-v1450"><a class="header" href="#upgrading-to-v1450">Upgrading to v1.45.0</a></h1>
+<h2 id="changes-required-to-media-storage-provider-modules-when-reading-from-the-synapse-configuration-object"><a class="header" href="#changes-required-to-media-storage-provider-modules-when-reading-from-the-synapse-configuration-object">Changes required to media storage provider modules when reading from the Synapse configuration object</a></h2>
+<p>Media storage provider modules that read from the Synapse configuration object (i.e. that
+read the value of <code>hs.config.[...]</code>) now need to specify the configuration section they're
+reading from. This means that if a module reads the value of e.g. <code>hs.config.media_store_path</code>,
+it needs to replace it with <code>hs.config.media.media_store_path</code>.</p>
+<h1 id="upgrading-to-v1440"><a class="header" href="#upgrading-to-v1440">Upgrading to v1.44.0</a></h1>
+<h2 id="the-url-preview-cache-is-no-longer-mirrored-to-storage-providers"><a class="header" href="#the-url-preview-cache-is-no-longer-mirrored-to-storage-providers">The URL preview cache is no longer mirrored to storage providers</a></h2>
+<p>The <code>url_cache/</code> and <code>url_cache_thumbnails/</code> directories in the media store are
+no longer mirrored to storage providers. These two directories can be safely
+deleted from any configured storage providers to reclaim space.</p>
+<h1 id="upgrading-to-v1430"><a class="header" href="#upgrading-to-v1430">Upgrading to v1.43.0</a></h1>
+<h2 id="the-spaces-summary-apis-can-now-be-handled-by-workers"><a class="header" href="#the-spaces-summary-apis-can-now-be-handled-by-workers">The spaces summary APIs can now be handled by workers</a></h2>
+<p>The <a href="https://matrix-org.github.io/synapse/latest/workers.html#available-worker-applications">available worker applications documentation</a>
+has been updated to reflect that calls to the <code>/spaces</code>, <code>/hierarchy</code>, and
+<code>/summary</code> endpoints can now be routed to workers for both client API and
+federation requests.</p>
+<h1 id="upgrading-to-v1420"><a class="header" href="#upgrading-to-v1420">Upgrading to v1.42.0</a></h1>
+<h2 id="removal-of-old-room-admin-api-1"><a class="header" href="#removal-of-old-room-admin-api-1">Removal of old Room Admin API</a></h2>
+<p>The following admin APIs were deprecated in <a href="https://github.com/matrix-org/synapse/blob/v1.25.0/CHANGES.md#removal-warning">Synapse 1.25</a>
+(released on 2021-01-13) and have now been removed:</p>
+<ul>
+<li><code>POST /_synapse/admin/v1/purge_room</code></li>
+<li><code>POST /_synapse/admin/v1/shutdown_room/&lt;room_id&gt;</code></li>
+</ul>
+<p>Any scripts still using the above APIs should be converted to use the
+<a href="https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-room-api">Delete Room API</a>.</p>
+<h2 id="user-interactive-authentication-fallback-templates-can-now-display-errors"><a class="header" href="#user-interactive-authentication-fallback-templates-can-now-display-errors">User-interactive authentication fallback templates can now display errors</a></h2>
+<p>This may affect you if you make use of custom HTML templates for the
+<a href="../synapse/res/templates/recaptcha.html">reCAPTCHA</a> or
+<a href="../synapse/res/templates/terms.html">terms</a> fallback pages.</p>
+<p>The template is now provided an <code>error</code> variable if the authentication
+process failed. See the default templates linked above for an example.</p>
+<h2 id="removal-of-out-of-date-email-pushers"><a class="header" href="#removal-of-out-of-date-email-pushers">Removal of out-of-date email pushers</a></h2>
+<p>Users will stop receiving message updates via email for addresses that were
+once, but not still, linked to their account.</p>
+<h1 id="upgrading-to-v1410"><a class="header" href="#upgrading-to-v1410">Upgrading to v1.41.0</a></h1>
+<h2 id="add-support-for-routing-outbound-http-requests-via-a-proxy-for-federation"><a class="header" href="#add-support-for-routing-outbound-http-requests-via-a-proxy-for-federation">Add support for routing outbound HTTP requests via a proxy for federation</a></h2>
+<p>Since Synapse 1.6.0 (2019-11-26) you can set a proxy for outbound HTTP requests via
+http_proxy/https_proxy environment variables. This proxy was set for:</p>
+<ul>
+<li>push</li>
+<li>url previews</li>
+<li>phone-home stats</li>
+<li>recaptcha validation</li>
+<li>CAS auth validation</li>
+<li>OpenID Connect</li>
+<li>Federation (checking public key revocation)</li>
+</ul>
+<p>In this version we have added support for outbound requests for:</p>
+<ul>
+<li>Outbound federation</li>
+<li>Downloading remote media</li>
+<li>Fetching public keys of other servers</li>
+</ul>
+<p>These requests use the same proxy configuration. If you have a proxy configuration we
+recommend to verify the configuration. It may be necessary to adjust the <code>no_proxy</code>
+environment variable.</p>
+<p>See <a href="setup/forward_proxy.html">using a forward proxy with Synapse documentation</a> for
+details.</p>
+<h2 id="deprecation-of-template_dir"><a class="header" href="#deprecation-of-template_dir">Deprecation of <code>template_dir</code></a></h2>
+<p>The <code>template_dir</code> settings in the <code>sso</code>, <code>account_validity</code> and <code>email</code> sections of the
+configuration file are now deprecated. Server admins should use the new
+<code>templates.custom_template_directory</code> setting in the configuration file and use one single
+custom template directory for all aforementioned features. Template file names remain
+unchanged. See <a href="https://matrix-org.github.io/synapse/latest/templates.html">the related documentation</a>
+for more information and examples.</p>
+<p>We plan to remove support for these settings in October 2021.</p>
+<h2 id="_synapseadminv1usersuseridmedia-must-be-handled-by-media-workers"><a class="header" href="#_synapseadminv1usersuseridmedia-must-be-handled-by-media-workers"><code>/_synapse/admin/v1/users/{userId}/media</code> must be handled by media workers</a></h2>
+<p>The <a href="https://matrix-org.github.io/synapse/latest/workers.html#synapseappmedia_repository">media repository worker documentation</a>
+has been updated to reflect that calls to <code>/_synapse/admin/v1/users/{userId}/media</code>
+must now be handled by media repository workers. This is due to the new <code>DELETE</code> method
+of this endpoint modifying the media store.</p>
+<h1 id="upgrading-to-v1390"><a class="header" href="#upgrading-to-v1390">Upgrading to v1.39.0</a></h1>
+<h2 id="deprecation-of-the-current-third-party-rules-module-interface"><a class="header" href="#deprecation-of-the-current-third-party-rules-module-interface">Deprecation of the current third-party rules module interface</a></h2>
+<p>The current third-party rules module interface is deprecated in favour of the new generic
+modules system introduced in Synapse v1.37.0. Authors of third-party rules modules can refer
+to <a href="modules/porting_legacy_module.html">this documentation</a>
+to update their modules. Synapse administrators can refer to <a href="modules/index.html">this documentation</a>
+to update their configuration once the modules they are using have been updated.</p>
+<p>We plan to remove support for the current third-party rules interface in September 2021.</p>
+<h1 id="upgrading-to-v1380"><a class="header" href="#upgrading-to-v1380">Upgrading to v1.38.0</a></h1>
+<h2 id="re-indexing-of-events-table-on-postgres-databases"><a class="header" href="#re-indexing-of-events-table-on-postgres-databases">Re-indexing of <code>events</code> table on Postgres databases</a></h2>
+<p>This release includes a database schema update which requires re-indexing one of
+the larger tables in the database, <code>events</code>. This could result in increased
+disk I/O for several hours or days after upgrading while the migration
+completes. Furthermore, because we have to keep the old indexes until the new
+indexes are ready, it could result in a significant, temporary, increase in
+disk space.</p>
+<p>To get a rough idea of the disk space required, check the current size of one
+of the indexes. For example, from a <code>psql</code> shell, run the following sql:</p>
+<pre><code class="language-sql">SELECT pg_size_pretty(pg_relation_size('events_order_room'));
+</code></pre>
+<p>We need to rebuild <strong>four</strong> indexes, so you will need to multiply this result
+by four to give an estimate of the disk space required. For example, on one
+particular server:</p>
+<pre><code>synapse=# select pg_size_pretty(pg_relation_size('events_order_room'));
+ pg_size_pretty
+----------------
+ 288 MB
+(1 row)
+</code></pre>
+<p>On this server, it would be wise to ensure that at least 1152MB are free.</p>
+<p>The additional disk space will be freed once the migration completes.</p>
+<p>SQLite databases are unaffected by this change.</p>
+<h1 id="upgrading-to-v1370"><a class="header" href="#upgrading-to-v1370">Upgrading to v1.37.0</a></h1>
+<h2 id="deprecation-of-the-current-spam-checker-interface"><a class="header" href="#deprecation-of-the-current-spam-checker-interface">Deprecation of the current spam checker interface</a></h2>
+<p>The current spam checker interface is deprecated in favour of a new generic modules system.
+Authors of spam checker modules can refer to [this
+documentation](modules/porting_legacy_module.md
+to update their modules. Synapse administrators can refer to <a href="modules/index.html">this
+documentation</a>
+to update their configuration once the modules they are using have been updated.</p>
+<p>We plan to remove support for the current spam checker interface in August 2021.</p>
+<p>More module interfaces will be ported over to this new generic system in future versions
+of Synapse.</p>
+<h1 id="upgrading-to-v1340"><a class="header" href="#upgrading-to-v1340">Upgrading to v1.34.0</a></h1>
+<h2 id="room_invite_state_types-configuration-setting"><a class="header" href="#room_invite_state_types-configuration-setting"><code>room_invite_state_types</code> configuration setting</a></h2>
+<p>The <code>room_invite_state_types</code> configuration setting has been deprecated
+and replaced with <code>room_prejoin_state</code>. See the <a href="https://github.com/matrix-org/synapse/blob/v1.34.0/docs/sample_config.yaml#L1515">sample configuration
+file</a>.</p>
+<p>If you have set <code>room_invite_state_types</code> to the default value you
+should simply remove it from your configuration file. The default value
+used to be:</p>
+<pre><code class="language-yaml">room_invite_state_types:
+   - &quot;m.room.join_rules&quot;
+   - &quot;m.room.canonical_alias&quot;
+   - &quot;m.room.avatar&quot;
+   - &quot;m.room.encryption&quot;
+   - &quot;m.room.name&quot;
+</code></pre>
+<p>If you have customised this value, you should remove
+<code>room_invite_state_types</code> and configure <code>room_prejoin_state</code> instead.</p>
+<h1 id="upgrading-to-v1330"><a class="header" href="#upgrading-to-v1330">Upgrading to v1.33.0</a></h1>
+<h2 id="account-validity-html-templates-can-now-display-a-users-expiration-date"><a class="header" href="#account-validity-html-templates-can-now-display-a-users-expiration-date">Account Validity HTML templates can now display a user's expiration date</a></h2>
+<p>This may affect you if you have enabled the account validity feature,
+and have made use of a custom HTML template specified by the
+<code>account_validity.template_dir</code> or
+<code>account_validity.account_renewed_html_path</code> Synapse config options.</p>
+<p>The template can now accept an <code>expiration_ts</code> variable, which
+represents the unix timestamp in milliseconds for the future date of
+which their account has been renewed until. See the <a href="https://github.com/matrix-org/synapse/blob/release-v1.33.0/synapse/res/templates/account_renewed.html">default
+template</a>
+for an example of usage.</p>
+<p>ALso note that a new HTML template, <code>account_previously_renewed.html</code>,
+has been added. This is is shown to users when they attempt to renew
+their account with a valid renewal token that has already been used
+before. The default template contents can been found
+<a href="https://github.com/matrix-org/synapse/blob/release-v1.33.0/synapse/res/templates/account_previously_renewed.html">here</a>,
+and can also accept an <code>expiration_ts</code> variable. This template replaces
+the error message users would previously see upon attempting to use a
+valid renewal token more than once.</p>
+<h1 id="upgrading-to-v1320"><a class="header" href="#upgrading-to-v1320">Upgrading to v1.32.0</a></h1>
+<h2 id="regression-causing-connected-prometheus-instances-to-become-overwhelmed"><a class="header" href="#regression-causing-connected-prometheus-instances-to-become-overwhelmed">Regression causing connected Prometheus instances to become overwhelmed</a></h2>
+<p>This release introduces <a href="https://github.com/matrix-org/synapse/issues/9853">a
+regression</a> that can
+overwhelm connected Prometheus instances. This issue is not present in
+Synapse v1.32.0rc1.</p>
+<p>If you have been affected, please downgrade to 1.31.0. You then may need
+to remove excess writeahead logs in order for Prometheus to recover.
+Instructions for doing so are provided
+<a href="https://github.com/matrix-org/synapse/pull/9854#issuecomment-823472183">here</a>.</p>
+<h2 id="dropping-support-for-old-python-postgres-and-sqlite-versions"><a class="header" href="#dropping-support-for-old-python-postgres-and-sqlite-versions">Dropping support for old Python, Postgres and SQLite versions</a></h2>
+<p>In line with our <a href="deprecation_policy.html">deprecation policy</a>,
+we've dropped support for Python 3.5 and PostgreSQL 9.5, as they are no
+longer supported upstream.</p>
+<p>This release of Synapse requires Python 3.6+ and PostgresSQL 9.6+ or
+SQLite 3.22+.</p>
+<h2 id="removal-of-old-list-accounts-admin-api"><a class="header" href="#removal-of-old-list-accounts-admin-api">Removal of old List Accounts Admin API</a></h2>
+<p>The deprecated v1 &quot;list accounts&quot; admin API
+(<code>GET /_synapse/admin/v1/users/&lt;user_id&gt;</code>) has been removed in this
+version.</p>
+<p>The <a href="admin_api/user_admin_api.html#list-accounts">v2 list accounts API</a>
+has been available since Synapse 1.7.0 (2019-12-13), and is accessible
+under <code>GET /_synapse/admin/v2/users</code>.</p>
+<p>The deprecation of the old endpoint was announced with Synapse 1.28.0
+(released on 2021-02-25).</p>
+<h2 id="application-services-must-use-type-mloginapplication_service-when-registering-users"><a class="header" href="#application-services-must-use-type-mloginapplication_service-when-registering-users">Application Services must use type <code>m.login.application_service</code> when registering users</a></h2>
+<p>In compliance with the <a href="https://matrix.org/docs/spec/application_service/r0.1.2#server-admin-style-permissions">Application Service
+spec</a>,
+Application Services are now required to use the
+<code>m.login.application_service</code> type when registering users via the
+<code>/_matrix/client/r0/register</code> endpoint. This behaviour was deprecated in
+Synapse v1.30.0.</p>
+<p>Please ensure your Application Services are up to date.</p>
+<h1 id="upgrading-to-v1290"><a class="header" href="#upgrading-to-v1290">Upgrading to v1.29.0</a></h1>
+<h2 id="requirement-for-x-forwarded-proto-header"><a class="header" href="#requirement-for-x-forwarded-proto-header">Requirement for X-Forwarded-Proto header</a></h2>
+<p>When using Synapse with a reverse proxy (in particular, when using the
+<code>x_forwarded</code> option on an HTTP listener), Synapse now
+expects to receive an <code>X-Forwarded-Proto</code> header on incoming
+HTTP requests. If it is not set, Synapse will log a warning on each
+received request.</p>
+<p>To avoid the warning, administrators using a reverse proxy should ensure
+that the reverse proxy sets <code>X-Forwarded-Proto</code> header to
+<code>https</code> or <code>http</code> to indicate the protocol used
+by the client.</p>
+<p>Synapse also requires the <code>Host</code> header to be preserved.</p>
+<p>See the <a href="reverse_proxy.html">reverse proxy documentation</a>, where the
+example configurations have been updated to show how to set these
+headers.</p>
+<p>(Users of <a href="https://caddyserver.com/">Caddy</a> are unaffected, since we
+believe it sets <code>X-Forwarded-Proto</code> by default.)</p>
+<h1 id="upgrading-to-v1270"><a class="header" href="#upgrading-to-v1270">Upgrading to v1.27.0</a></h1>
+<h2 id="changes-to-callback-uri-for-oauth2--openid-connect-and-saml2"><a class="header" href="#changes-to-callback-uri-for-oauth2--openid-connect-and-saml2">Changes to callback URI for OAuth2 / OpenID Connect and SAML2</a></h2>
+<p>This version changes the URI used for callbacks from OAuth2 and SAML2
+identity providers:</p>
+<ul>
+<li>
+<p>If your server is configured for single sign-on via an OpenID
+Connect or OAuth2 identity provider, you will need to add
+<code>[synapse public baseurl]/_synapse/client/oidc/callback</code> to the list
+of permitted &quot;redirect URIs&quot; at the identity provider.</p>
+<p>See the <a href="openid.html">OpenID docs</a> for more information on setting
+up OpenID Connect.</p>
+</li>
+<li>
+<p>If your server is configured for single sign-on via a SAML2 identity
+provider, you will need to add
+<code>[synapse public baseurl]/_synapse/client/saml2/authn_response</code> as a
+permitted &quot;ACS location&quot; (also known as &quot;allowed callback URLs&quot;)
+at the identity provider.</p>
+<p>The &quot;Issuer&quot; in the &quot;AuthnRequest&quot; to the SAML2 identity
+provider is also updated to
+<code>[synapse public baseurl]/_synapse/client/saml2/metadata.xml</code>. If
+your SAML2 identity provider uses this property to validate or
+otherwise identify Synapse, its configuration will need to be
+updated to use the new URL. Alternatively you could create a new,
+separate &quot;EntityDescriptor&quot; in your SAML2 identity provider with
+the new URLs and leave the URLs in the existing &quot;EntityDescriptor&quot;
+as they were.</p>
+</li>
+</ul>
+<h2 id="changes-to-html-templates"><a class="header" href="#changes-to-html-templates">Changes to HTML templates</a></h2>
+<p>The HTML templates for SSO and email notifications now have <a href="https://jinja.palletsprojects.com/en/2.11.x/api/#autoescaping">Jinja2's
+autoescape</a>
+enabled for files ending in <code>.html</code>, <code>.htm</code>, and <code>.xml</code>. If you have
+customised these templates and see issues when viewing them you might
+need to update them. It is expected that most configurations will need
+no changes.</p>
+<p>If you have customised the templates <em>names</em> for these templates, it is
+recommended to verify they end in <code>.html</code> to ensure autoescape is
+enabled.</p>
+<p>The above applies to the following templates:</p>
+<ul>
+<li><code>add_threepid.html</code></li>
+<li><code>add_threepid_failure.html</code></li>
+<li><code>add_threepid_success.html</code></li>
+<li><code>notice_expiry.html</code></li>
+<li><code>notice_expiry.html</code></li>
+<li><code>notif_mail.html</code> (which, by default, includes <code>room.html</code> and
+<code>notif.html</code>)</li>
+<li><code>password_reset.html</code></li>
+<li><code>password_reset_confirmation.html</code></li>
+<li><code>password_reset_failure.html</code></li>
+<li><code>password_reset_success.html</code></li>
+<li><code>registration.html</code></li>
+<li><code>registration_failure.html</code></li>
+<li><code>registration_success.html</code></li>
+<li><code>sso_account_deactivated.html</code></li>
+<li><code>sso_auth_bad_user.html</code></li>
+<li><code>sso_auth_confirm.html</code></li>
+<li><code>sso_auth_success.html</code></li>
+<li><code>sso_error.html</code></li>
+<li><code>sso_login_idp_picker.html</code></li>
+<li><code>sso_redirect_confirm.html</code></li>
+</ul>
+<h1 id="upgrading-to-v1260"><a class="header" href="#upgrading-to-v1260">Upgrading to v1.26.0</a></h1>
+<h2 id="rolling-back-to-v1250-after-a-failed-upgrade"><a class="header" href="#rolling-back-to-v1250-after-a-failed-upgrade">Rolling back to v1.25.0 after a failed upgrade</a></h2>
+<p>v1.26.0 includes a lot of large changes. If something problematic
+occurs, you may want to roll-back to a previous version of Synapse.
+Because v1.26.0 also includes a new database schema version, reverting
+that version is also required alongside the generic rollback
+instructions mentioned above. In short, to roll back to v1.25.0 you need
+to:</p>
+<ol>
+<li>
+<p>Stop the server</p>
+</li>
+<li>
+<p>Decrease the schema version in the database:</p>
+<pre><code class="language-sql">UPDATE schema_version SET version = 58;
+</code></pre>
+</li>
+<li>
+<p>Delete the ignored users &amp; chain cover data:</p>
+<pre><code class="language-sql">DROP TABLE IF EXISTS ignored_users;
+UPDATE rooms SET has_auth_chain_index = false;
+</code></pre>
+<p>For PostgreSQL run:</p>
+<pre><code class="language-sql">TRUNCATE event_auth_chain_links;
+TRUNCATE event_auth_chains;
+</code></pre>
+<p>For SQLite run:</p>
+<pre><code class="language-sql">DELETE FROM event_auth_chain_links;
+DELETE FROM event_auth_chains;
+</code></pre>
+</li>
+<li>
+<p>Mark the deltas as not run (so they will re-run on upgrade).</p>
+<pre><code class="language-sql">DELETE FROM applied_schema_deltas WHERE version = 59 AND file = &quot;59/01ignored_user.py&quot;;
+DELETE FROM applied_schema_deltas WHERE version = 59 AND file = &quot;59/06chain_cover_index.sql&quot;;
+</code></pre>
+</li>
+<li>
+<p>Downgrade Synapse by following the instructions for your
+installation method in the &quot;Rolling back to older versions&quot;
+section above.</p>
+</li>
+</ol>
+<h1 id="upgrading-to-v1250"><a class="header" href="#upgrading-to-v1250">Upgrading to v1.25.0</a></h1>
+<h2 id="last-release-supporting-python-35"><a class="header" href="#last-release-supporting-python-35">Last release supporting Python 3.5</a></h2>
+<p>This is the last release of Synapse which guarantees support with Python
+3.5, which passed its upstream End of Life date several months ago.</p>
+<p>We will attempt to maintain support through March 2021, but without
+guarantees.</p>
+<p>In the future, Synapse will follow upstream schedules for ending support
+of older versions of Python and PostgreSQL. Please upgrade to at least
+Python 3.6 and PostgreSQL 9.6 as soon as possible.</p>
+<h2 id="blacklisting-ip-ranges"><a class="header" href="#blacklisting-ip-ranges">Blacklisting IP ranges</a></h2>
+<p>Synapse v1.25.0 includes new settings, <code>ip_range_blacklist</code> and
+<code>ip_range_whitelist</code>, for controlling outgoing requests from Synapse for
+federation, identity servers, push, and for checking key validity for
+third-party invite events. The previous setting,
+<code>federation_ip_range_blacklist</code>, is deprecated. The new
+<code>ip_range_blacklist</code> defaults to private IP ranges if it is not defined.</p>
+<p>If you have never customised <code>federation_ip_range_blacklist</code> it is
+recommended that you remove that setting.</p>
+<p>If you have customised <code>federation_ip_range_blacklist</code> you should update
+the setting name to <code>ip_range_blacklist</code>.</p>
+<p>If you have a custom push server that is reached via private IP space
+you may need to customise <code>ip_range_blacklist</code> or <code>ip_range_whitelist</code>.</p>
+<h1 id="upgrading-to-v1240"><a class="header" href="#upgrading-to-v1240">Upgrading to v1.24.0</a></h1>
+<h2 id="custom-openid-connect-mapping-provider-breaking-change"><a class="header" href="#custom-openid-connect-mapping-provider-breaking-change">Custom OpenID Connect mapping provider breaking change</a></h2>
+<p>This release allows the OpenID Connect mapping provider to perform
+normalisation of the localpart of the Matrix ID. This allows for the
+mapping provider to specify different algorithms, instead of the
+<a href="https://matrix.org/docs/spec/appendices#mapping-from-other-character-sets">default
+way</a>.</p>
+<p>If your Synapse configuration uses a custom mapping provider
+(<code>oidc_config.user_mapping_provider.module</code> is specified and
+not equal to
+<code>synapse.handlers.oidc_handler.JinjaOidcMappingProvider</code>)
+then you <em>must</em> ensure that <code>map_user_attributes</code> of the
+mapping provider performs some normalisation of the
+<code>localpart</code> returned. To match previous behaviour you can
+use the <code>map_username_to_mxid_localpart</code> function provided
+by Synapse. An example is shown below:</p>
+<pre><code class="language-python">from synapse.types import map_username_to_mxid_localpart
+
+class MyMappingProvider:
+    def map_user_attributes(self, userinfo, token):
+        # ... your custom logic ...
+        sso_user_id = ...
+        localpart = map_username_to_mxid_localpart(sso_user_id)
+
+        return {&quot;localpart&quot;: localpart}
+</code></pre>
+<h2 id="removal-historical-synapse-admin-api"><a class="header" href="#removal-historical-synapse-admin-api">Removal historical Synapse Admin API</a></h2>
+<p>Historically, the Synapse Admin API has been accessible under:</p>
+<ul>
+<li><code>/_matrix/client/api/v1/admin</code></li>
+<li><code>/_matrix/client/unstable/admin</code></li>
+<li><code>/_matrix/client/r0/admin</code></li>
+<li><code>/_synapse/admin/v1</code></li>
+</ul>
+<p>The endpoints with <code>/_matrix/client/*</code> prefixes have been removed as of
+v1.24.0. The Admin API is now only accessible under:</p>
+<ul>
+<li><code>/_synapse/admin/v1</code></li>
+</ul>
+<p>The only exception is the <code>/admin/whois</code> endpoint, which is
+<a href="https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid">also available via the client-server
+API</a>.</p>
+<p>The deprecation of the old endpoints was announced with Synapse 1.20.0
+(released on 2020-09-22) and makes it easier for homeserver admins to
+lock down external access to the Admin API endpoints.</p>
+<h1 id="upgrading-to-v1230"><a class="header" href="#upgrading-to-v1230">Upgrading to v1.23.0</a></h1>
+<h2 id="structured-logging-configuration-breaking-changes"><a class="header" href="#structured-logging-configuration-breaking-changes">Structured logging configuration breaking changes</a></h2>
+<p>This release deprecates use of the <code>structured: true</code> logging
+configuration for structured logging. If your logging configuration
+contains <code>structured: true</code> then it should be modified based on the
+<a href="structured_logging.html">structured logging documentation</a>.</p>
+<p>The <code>structured</code> and <code>drains</code> logging options are now deprecated and
+should be replaced by standard logging configuration of <code>handlers</code> and
+<code>formatters</code>.</p>
+<p>A future will release of Synapse will make using <code>structured: true</code> an
+error.</p>
+<h1 id="upgrading-to-v1220"><a class="header" href="#upgrading-to-v1220">Upgrading to v1.22.0</a></h1>
+<h2 id="thirdpartyeventrules-breaking-changes"><a class="header" href="#thirdpartyeventrules-breaking-changes">ThirdPartyEventRules breaking changes</a></h2>
+<p>This release introduces a backwards-incompatible change to modules
+making use of <code>ThirdPartyEventRules</code> in Synapse. If you make use of a
+module defined under the <code>third_party_event_rules</code> config option, please
+make sure it is updated to handle the below change:</p>
+<p>The <code>http_client</code> argument is no longer passed to modules as they are
+initialised. Instead, modules are expected to make use of the
+<code>http_client</code> property on the <code>ModuleApi</code> class. Modules are now passed
+a <code>module_api</code> argument during initialisation, which is an instance of
+<code>ModuleApi</code>. <code>ModuleApi</code> instances have a <code>http_client</code> property which
+acts the same as the <code>http_client</code> argument previously passed to
+<code>ThirdPartyEventRules</code> modules.</p>
+<h1 id="upgrading-to-v1210"><a class="header" href="#upgrading-to-v1210">Upgrading to v1.21.0</a></h1>
+<h2 id="forwarding-_synapseclient-through-your-reverse-proxy"><a class="header" href="#forwarding-_synapseclient-through-your-reverse-proxy">Forwarding <code>/_synapse/client</code> through your reverse proxy</a></h2>
+<p>The <a href="reverse_proxy.html">reverse proxy documentation</a>
+has been updated to include reverse proxy directives for
+<code>/_synapse/client/*</code> endpoints. As the user password reset flow now uses
+endpoints under this prefix, <strong>you must update your reverse proxy
+configurations for user password reset to work</strong>.</p>
+<p>Additionally, note that the <a href="workers.html">Synapse worker documentation</a> has been updated to</p>
+<p>:   state that the <code>/_synapse/client/password_reset/email/submit_token</code>
+endpoint can be handled</p>
+<p>by all workers. If you make use of Synapse's worker feature, please
+update your reverse proxy configuration to reflect this change.</p>
+<h2 id="new-html-templates"><a class="header" href="#new-html-templates">New HTML templates</a></h2>
+<p>A new HTML template,
+<a href="https://github.com/matrix-org/synapse/blob/develop/synapse/res/templates/password_reset_confirmation.html">password_reset_confirmation.html</a>,
+has been added to the <code>synapse/res/templates</code> directory. If you are
+using a custom template directory, you may want to copy the template
+over and modify it.</p>
+<p>Note that as of v1.20.0, templates do not need to be included in custom
+template directories for Synapse to start. The default templates will be
+used if a custom template cannot be found.</p>
+<p>This page will appear to the user after clicking a password reset link
+that has been emailed to them.</p>
+<p>To complete password reset, the page must include a way to make a
+<code>POST</code> request to
+<code>/_synapse/client/password_reset/{medium}/submit_token</code> with the query
+parameters from the original link, presented as a URL-encoded form. See
+the file itself for more details.</p>
+<h2 id="updated-single-sign-on-html-templates"><a class="header" href="#updated-single-sign-on-html-templates">Updated Single Sign-on HTML Templates</a></h2>
+<p>The <code>saml_error.html</code> template was removed from Synapse and replaced
+with the <code>sso_error.html</code> template. If your Synapse is configured to use
+SAML and a custom <code>sso_redirect_confirm_template_dir</code> configuration then
+any customisations of the <code>saml_error.html</code> template will need to be
+merged into the <code>sso_error.html</code> template. These templates are similar,
+but the parameters are slightly different:</p>
+<ul>
+<li>The <code>msg</code> parameter should be renamed to <code>error_description</code>.</li>
+<li>There is no longer a <code>code</code> parameter for the response code.</li>
+<li>A string <code>error</code> parameter is available that includes a short hint
+of why a user is seeing the error page.</li>
+</ul>
+<h1 id="upgrading-to-v1180"><a class="header" href="#upgrading-to-v1180">Upgrading to v1.18.0</a></h1>
+<h2 id="docker--py3-suffix-will-be-removed-in-future-versions"><a class="header" href="#docker--py3-suffix-will-be-removed-in-future-versions">Docker <code>-py3</code> suffix will be removed in future versions</a></h2>
+<p>From 10th August 2020, we will no longer publish Docker images with the
+<code>-py3</code> tag suffix. The images tagged with the
+<code>-py3</code> suffix have been identical to the non-suffixed tags
+since release 0.99.0, and the suffix is obsolete.</p>
+<p>On 10th August, we will remove the <code>latest-py3</code> tag.
+Existing per-release tags (such as <code>v1.18.0-py3</code> will not
+be removed, but no new <code>-py3</code> tags will be added.</p>
+<p>Scripts relying on the <code>-py3</code> suffix will need to be
+updated.</p>
+<h2 id="redis-replication-is-now-recommended-in-lieu-of-tcp-replication"><a class="header" href="#redis-replication-is-now-recommended-in-lieu-of-tcp-replication">Redis replication is now recommended in lieu of TCP replication</a></h2>
+<p>When setting up worker processes, we now recommend the use of a Redis
+server for replication. <strong>The old direct TCP connection method is
+deprecated and will be removed in a future release.</strong> See
+<a href="workers.html">workers</a> for more details.</p>
+<h1 id="upgrading-to-v1140"><a class="header" href="#upgrading-to-v1140">Upgrading to v1.14.0</a></h1>
+<p>This version includes a database update which is run as part of the
+upgrade, and which may take a couple of minutes in the case of a large
+server. Synapse will not respond to HTTP requests while this update is
+taking place.</p>
+<h1 id="upgrading-to-v1130"><a class="header" href="#upgrading-to-v1130">Upgrading to v1.13.0</a></h1>
+<h2 id="incorrect-database-migration-in-old-synapse-versions"><a class="header" href="#incorrect-database-migration-in-old-synapse-versions">Incorrect database migration in old synapse versions</a></h2>
+<p>A bug was introduced in Synapse 1.4.0 which could cause the room
+directory to be incomplete or empty if Synapse was upgraded directly
+from v1.2.1 or earlier, to versions between v1.4.0 and v1.12.x.</p>
+<p>This will <em>not</em> be a problem for Synapse installations which were:</p>
+<p>:   -   created at v1.4.0 or later,
+-   upgraded via v1.3.x, or
+-   upgraded straight from v1.2.1 or earlier to v1.13.0 or later.</p>
+<p>If completeness of the room directory is a concern, installations which
+are affected can be repaired as follows:</p>
+<ol>
+<li>
+<p>Run the following sql from a <code>psql</code> or
+<code>sqlite3</code> console:</p>
+<pre><code class="language-sql">INSERT INTO background_updates (update_name, progress_json, depends_on) VALUES
+   ('populate_stats_process_rooms', '{}', 'current_state_events_membership');
+
+INSERT INTO background_updates (update_name, progress_json, depends_on) VALUES
+   ('populate_stats_process_users', '{}', 'populate_stats_process_rooms');
+</code></pre>
+</li>
+<li>
+<p>Restart synapse.</p>
+</li>
+</ol>
+<h2 id="new-single-sign-on-html-templates"><a class="header" href="#new-single-sign-on-html-templates">New Single Sign-on HTML Templates</a></h2>
+<p>New templates (<code>sso_auth_confirm.html</code>, <code>sso_auth_success.html</code>, and
+<code>sso_account_deactivated.html</code>) were added to Synapse. If your Synapse
+is configured to use SSO and a custom
+<code>sso_redirect_confirm_template_dir</code> configuration then these templates
+will need to be copied from
+<a href="synapse/res/templates">synapse/res/templates</a> into that directory.</p>
+<h2 id="synapse-sso-plugins-method-deprecation"><a class="header" href="#synapse-sso-plugins-method-deprecation">Synapse SSO Plugins Method Deprecation</a></h2>
+<p>Plugins using the <code>complete_sso_login</code> method of
+<code>synapse.module_api.ModuleApi</code> should update to using the async/await
+version <code>complete_sso_login_async</code> which includes additional checks. The
+non-async version is considered deprecated.</p>
+<h2 id="rolling-back-to-v1124-after-a-failed-upgrade"><a class="header" href="#rolling-back-to-v1124-after-a-failed-upgrade">Rolling back to v1.12.4 after a failed upgrade</a></h2>
+<p>v1.13.0 includes a lot of large changes. If something problematic
+occurs, you may want to roll-back to a previous version of Synapse.
+Because v1.13.0 also includes a new database schema version, reverting
+that version is also required alongside the generic rollback
+instructions mentioned above. In short, to roll back to v1.12.4 you need
+to:</p>
+<ol>
+<li>
+<p>Stop the server</p>
+</li>
+<li>
+<p>Decrease the schema version in the database:</p>
+<pre><code class="language-sql">UPDATE schema_version SET version = 57;
+</code></pre>
+</li>
+<li>
+<p>Downgrade Synapse by following the instructions for your
+installation method in the &quot;Rolling back to older versions&quot;
+section above.</p>
+</li>
+</ol>
+<h1 id="upgrading-to-v1120"><a class="header" href="#upgrading-to-v1120">Upgrading to v1.12.0</a></h1>
+<p>This version includes a database update which is run as part of the
+upgrade, and which may take some time (several hours in the case of a
+large server). Synapse will not respond to HTTP requests while this
+update is taking place.</p>
+<p>This is only likely to be a problem in the case of a server which is
+participating in many rooms.</p>
+<ol start="0">
+<li>
+<p>As with all upgrades, it is recommended that you have a recent
+backup of your database which can be used for recovery in the event
+of any problems.</p>
+</li>
+<li>
+<p>As an initial check to see if you will be affected, you can try
+running the following query from the <code>psql</code> or
+<code>sqlite3</code> console. It is safe to run it while Synapse is
+still running.</p>
+<pre><code class="language-sql">SELECT MAX(q.v) FROM (
+  SELECT (
+    SELECT ej.json AS v
+    FROM state_events se INNER JOIN event_json ej USING (event_id)
+    WHERE se.room_id=rooms.room_id AND se.type='m.room.create' AND se.state_key=''
+    LIMIT 1
+  ) FROM rooms WHERE rooms.room_version IS NULL
+) q;
+</code></pre>
+<p>This query will take about the same amount of time as the upgrade
+process: ie, if it takes 5 minutes, then it is likely that Synapse
+will be unresponsive for 5 minutes during the upgrade.</p>
+<p>If you consider an outage of this duration to be acceptable, no
+further action is necessary and you can simply start Synapse 1.12.0.</p>
+<p>If you would prefer to reduce the downtime, continue with the steps
+below.</p>
+</li>
+<li>
+<p>The easiest workaround for this issue is to manually create a new
+index before upgrading. On PostgreSQL, his can be done as follows:</p>
+<pre><code class="language-sql">CREATE INDEX CONCURRENTLY tmp_upgrade_1_12_0_index
+ON state_events(room_id) WHERE type = 'm.room.create';
+</code></pre>
+<p>The above query may take some time, but is also safe to run while
+Synapse is running.</p>
+<p>We assume that no SQLite users have databases large enough to be
+affected. If you <em>are</em> affected, you can run a similar query,
+omitting the <code>CONCURRENTLY</code> keyword. Note however that this
+operation may in itself cause Synapse to stop running for some time.
+Synapse admins are reminded that <a href="postgres.html">SQLite is not recommended for use
+outside a test environment</a>.</p>
+</li>
+<li>
+<p>Once the index has been created, the <code>SELECT</code> query in step 1 above
+should complete quickly. It is therefore safe to upgrade to Synapse
+1.12.0.</p>
+</li>
+<li>
+<p>Once Synapse 1.12.0 has successfully started and is responding to
+HTTP requests, the temporary index can be removed:</p>
+<pre><code class="language-sql">DROP INDEX tmp_upgrade_1_12_0_index;
+</code></pre>
+</li>
+</ol>
+<h1 id="upgrading-to-v1100"><a class="header" href="#upgrading-to-v1100">Upgrading to v1.10.0</a></h1>
+<p>Synapse will now log a warning on start up if used with a PostgreSQL
+database that has a non-recommended locale set.</p>
+<p>See <a href="postgres.html">Postgres</a> for details.</p>
+<h1 id="upgrading-to-v180"><a class="header" href="#upgrading-to-v180">Upgrading to v1.8.0</a></h1>
+<p>Specifying a <code>log_file</code> config option will now cause Synapse to refuse
+to start, and should be replaced by with the <code>log_config</code> option.
+Support for the <code>log_file</code> option was removed in v1.3.0 and has since
+had no effect.</p>
+<h1 id="upgrading-to-v170"><a class="header" href="#upgrading-to-v170">Upgrading to v1.7.0</a></h1>
+<p>In an attempt to configure Synapse in a privacy preserving way, the
+default behaviours of <code>allow_public_rooms_without_auth</code> and
+<code>allow_public_rooms_over_federation</code> have been inverted. This means that
+by default, only authenticated users querying the Client/Server API will
+be able to query the room directory, and relatedly that the server will
+not share room directory information with other servers over federation.</p>
+<p>If your installation does not explicitly set these settings one way or
+the other and you want either setting to be <code>true</code> then it will
+necessary to update your homeserver configuration file accordingly.</p>
+<p>For more details on the surrounding context see our
+<a href="https://matrix.org/blog/2019/11/09/avoiding-unwelcome-visitors-on-private-matrix-servers">explainer</a>.</p>
+<h1 id="upgrading-to-v150"><a class="header" href="#upgrading-to-v150">Upgrading to v1.5.0</a></h1>
+<p>This release includes a database migration which may take several
+minutes to complete if there are a large number (more than a million or
+so) of entries in the <code>devices</code> table. This is only likely to a be a
+problem on very large installations.</p>
+<h1 id="upgrading-to-v140"><a class="header" href="#upgrading-to-v140">Upgrading to v1.4.0</a></h1>
+<h2 id="new-custom-templates"><a class="header" href="#new-custom-templates">New custom templates</a></h2>
+<p>If you have configured a custom template directory with the
+<code>email.template_dir</code> option, be aware that there are new templates
+regarding registration and threepid management (see below) that must be
+included.</p>
+<ul>
+<li><code>registration.html</code> and <code>registration.txt</code></li>
+<li><code>registration_success.html</code> and <code>registration_failure.html</code></li>
+<li><code>add_threepid.html</code> and <code>add_threepid.txt</code></li>
+<li><code>add_threepid_failure.html</code> and <code>add_threepid_success.html</code></li>
+</ul>
+<p>Synapse will expect these files to exist inside the configured template
+directory, and <strong>will fail to start</strong> if they are absent. To view the
+default templates, see
+<a href="https://github.com/matrix-org/synapse/tree/master/synapse/res/templates">synapse/res/templates</a>.</p>
+<h2 id="3pid-verification-changes"><a class="header" href="#3pid-verification-changes">3pid verification changes</a></h2>
+<p><strong>Note: As of this release, users will be unable to add phone numbers or
+email addresses to their accounts, without changes to the Synapse
+configuration. This includes adding an email address during
+registration.</strong></p>
+<p>It is possible for a user to associate an email address or phone number
+with their account, for a number of reasons:</p>
+<ul>
+<li>for use when logging in, as an alternative to the user id.</li>
+<li>in the case of email, as an alternative contact to help with account
+recovery.</li>
+<li>in the case of email, to receive notifications of missed messages.</li>
+</ul>
+<p>Before an email address or phone number can be added to a user's
+account, or before such an address is used to carry out a
+password-reset, Synapse must confirm the operation with the owner of the
+email address or phone number. It does this by sending an email or text
+giving the user a link or token to confirm receipt. This process is
+known as '3pid verification'. ('3pid', or 'threepid', stands for
+third-party identifier, and we use it to refer to external identifiers
+such as email addresses and phone numbers.)</p>
+<p>Previous versions of Synapse delegated the task of 3pid verification to
+an identity server by default. In most cases this server is <code>vector.im</code>
+or <code>matrix.org</code>.</p>
+<p>In Synapse 1.4.0, for security and privacy reasons, the homeserver will
+no longer delegate this task to an identity server by default. Instead,
+the server administrator will need to explicitly decide how they would
+like the verification messages to be sent.</p>
+<p>In the medium term, the <code>vector.im</code> and <code>matrix.org</code> identity servers
+will disable support for delegated 3pid verification entirely. However,
+in order to ease the transition, they will retain the capability for a
+limited period. Delegated email verification will be disabled on Monday
+2nd December 2019 (giving roughly 2 months notice). Disabling delegated
+SMS verification will follow some time after that once SMS verification
+support lands in Synapse.</p>
+<p>Once delegated 3pid verification support has been disabled in the
+<code>vector.im</code> and <code>matrix.org</code> identity servers, all Synapse versions that
+depend on those instances will be unable to verify email and phone
+numbers through them. There are no imminent plans to remove delegated
+3pid verification from Sydent generally. (Sydent is the identity server
+project that backs the <code>vector.im</code> and <code>matrix.org</code> instances).</p>
+<h3 id="email-1"><a class="header" href="#email-1">Email</a></h3>
+<p>Following upgrade, to continue verifying email (e.g. as part of the
+registration process), admins can either:-</p>
+<ul>
+<li>Configure Synapse to use an email server.</li>
+<li>Run or choose an identity server which allows delegated email
+verification and delegate to it.</li>
+</ul>
+<h4 id="configure-smtp-in-synapse"><a class="header" href="#configure-smtp-in-synapse">Configure SMTP in Synapse</a></h4>
+<p>To configure an SMTP server for Synapse, modify the configuration
+section headed <code>email</code>, and be sure to have at least the
+<code>smtp_host, smtp_port</code> and <code>notif_from</code> fields filled out.</p>
+<p>You may also need to set <code>smtp_user</code>, <code>smtp_pass</code>, and
+<code>require_transport_security</code>.</p>
+<p>See the <a href="usage/configuration/homeserver_sample_config.html">sample configuration file</a>
+for more details on these settings.</p>
+<h4 id="delegate-email-to-an-identity-server"><a class="header" href="#delegate-email-to-an-identity-server">Delegate email to an identity server</a></h4>
+<p>Some admins will wish to continue using email verification as part of
+the registration process, but will not immediately have an appropriate
+SMTP server at hand.</p>
+<p>To this end, we will continue to support email verification delegation
+via the <code>vector.im</code> and <code>matrix.org</code> identity servers for two months.
+Support for delegated email verification will be disabled on Monday 2nd
+December.</p>
+<p>The <code>account_threepid_delegates</code> dictionary defines whether the
+homeserver should delegate an external server (typically an <a href="https://matrix.org/docs/spec/identity_service/r0.2.1">identity
+server</a>) to handle
+sending confirmation messages via email and SMS.</p>
+<p>So to delegate email verification, in <code>homeserver.yaml</code>, set
+<code>account_threepid_delegates.email</code> to the base URL of an identity
+server. For example:</p>
+<pre><code class="language-yaml">account_threepid_delegates:
+    email: https://example.com     # Delegate email sending to example.com
+</code></pre>
+<p>Note that <code>account_threepid_delegates.email</code> replaces the deprecated
+<code>email.trust_identity_server_for_password_resets</code>: if
+<code>email.trust_identity_server_for_password_resets</code> is set to <code>true</code>, and
+<code>account_threepid_delegates.email</code> is not set, then the first entry in
+<code>trusted_third_party_id_servers</code> will be used as the
+<code>account_threepid_delegate</code> for email. This is to ensure compatibility
+with existing Synapse installs that set up external server handling for
+these tasks before v1.4.0. If
+<code>email.trust_identity_server_for_password_resets</code> is <code>true</code> and no
+trusted identity server domains are configured, Synapse will report an
+error and refuse to start.</p>
+<p>If <code>email.trust_identity_server_for_password_resets</code> is <code>false</code> or
+absent and no <code>email</code> delegate is configured in
+<code>account_threepid_delegates</code>, then Synapse will send email verification
+messages itself, using the configured SMTP server (see above). that
+type.</p>
+<h3 id="phone-numbers"><a class="header" href="#phone-numbers">Phone numbers</a></h3>
+<p>Synapse does not support phone-number verification itself, so the only
+way to maintain the ability for users to add phone numbers to their
+accounts will be by continuing to delegate phone number verification to
+the <code>matrix.org</code> and <code>vector.im</code> identity servers (or another identity
+server that supports SMS sending).</p>
+<p>The <code>account_threepid_delegates</code> dictionary defines whether the
+homeserver should delegate an external server (typically an <a href="https://matrix.org/docs/spec/identity_service/r0.2.1">identity
+server</a>) to handle
+sending confirmation messages via email and SMS.</p>
+<p>So to delegate phone number verification, in <code>homeserver.yaml</code>, set
+<code>account_threepid_delegates.msisdn</code> to the base URL of an identity
+server. For example:</p>
+<pre><code class="language-yaml">account_threepid_delegates:
+    msisdn: https://example.com     # Delegate sms sending to example.com
+</code></pre>
+<p>The <code>matrix.org</code> and <code>vector.im</code> identity servers will continue to
+support delegated phone number verification via SMS until such time as
+it is possible for admins to configure their servers to perform phone
+number verification directly. More details will follow in a future
+release.</p>
+<h2 id="rolling-back-to-v131"><a class="header" href="#rolling-back-to-v131">Rolling back to v1.3.1</a></h2>
+<p>If you encounter problems with v1.4.0, it should be possible to roll
+back to v1.3.1, subject to the following:</p>
+<ul>
+<li>
+<p>The 'room statistics' engine was heavily reworked in this release
+(see <a href="https://github.com/matrix-org/synapse/pull/5971">#5971</a>),
+including significant changes to the database schema, which are not
+easily reverted. This will cause the room statistics engine to stop
+updating when you downgrade.</p>
+<p>The room statistics are essentially unused in v1.3.1 (in future
+versions of Synapse, they will be used to populate the room
+directory), so there should be no loss of functionality. However,
+the statistics engine will write errors to the logs, which can be
+avoided by setting the following in <code>homeserver.yaml</code>:</p>
+<pre><code class="language-yaml">stats:
+  enabled: false
+</code></pre>
+<p>Don't forget to re-enable it when you upgrade again, in preparation
+for its use in the room directory!</p>
+</li>
+</ul>
+<h1 id="upgrading-to-v120"><a class="header" href="#upgrading-to-v120">Upgrading to v1.2.0</a></h1>
+<p>Some counter metrics have been renamed, with the old names deprecated.
+See <a href="metrics-howto.html#renaming-of-metrics--deprecation-of-old-names-in-12">the metrics
+documentation</a>
+for details.</p>
+<h1 id="upgrading-to-v110"><a class="header" href="#upgrading-to-v110">Upgrading to v1.1.0</a></h1>
+<p>Synapse v1.1.0 removes support for older Python and PostgreSQL versions,
+as outlined in <a href="https://matrix.org/blog/2019/04/08/synapse-deprecating-postgres-9-4-and-python-2-x">our deprecation
+notice</a>.</p>
+<h2 id="minimum-python-version"><a class="header" href="#minimum-python-version">Minimum Python Version</a></h2>
+<p>Synapse v1.1.0 has a minimum Python requirement of Python 3.5. Python
+3.6 or Python 3.7 are recommended as they have improved internal string
+handling, significantly reducing memory usage.</p>
+<p>If you use current versions of the Matrix.org-distributed Debian
+packages or Docker images, action is not required.</p>
+<p>If you install Synapse in a Python virtual environment, please see
+&quot;Upgrading to v0.34.0&quot; for notes on setting up a new virtualenv under
+Python 3.</p>
+<h2 id="minimum-postgresql-version"><a class="header" href="#minimum-postgresql-version">Minimum PostgreSQL Version</a></h2>
+<p>If using PostgreSQL under Synapse, you will need to use PostgreSQL 9.5
+or above. Please see the <a href="https://www.postgresql.org/docs/11/upgrading.html">PostgreSQL
+documentation</a> for
+more details on upgrading your database.</p>
+<h1 id="upgrading-to-v10"><a class="header" href="#upgrading-to-v10">Upgrading to v1.0</a></h1>
+<h2 id="validation-of-tls-certificates"><a class="header" href="#validation-of-tls-certificates">Validation of TLS certificates</a></h2>
+<p>Synapse v1.0 is the first release to enforce validation of TLS
+certificates for the federation API. It is therefore essential that your
+certificates are correctly configured.</p>
+<p>Note, v1.0 installations will also no longer be able to federate with
+servers that have not correctly configured their certificates.</p>
+<p>In rare cases, it may be desirable to disable certificate checking: for
+example, it might be essential to be able to federate with a given
+legacy server in a closed federation. This can be done in one of two
+ways:-</p>
+<ul>
+<li>Configure the global switch <code>federation_verify_certificates</code> to
+<code>false</code>.</li>
+<li>Configure a whitelist of server domains to trust via
+<code>federation_certificate_verification_whitelist</code>.</li>
+</ul>
+<p>See the <a href="usage/configuration/homeserver_sample_config.html">sample configuration file</a>
+for more details on these settings.</p>
+<h2 id="email-2"><a class="header" href="#email-2">Email</a></h2>
+<p>When a user requests a password reset, Synapse will send an email to the
+user to confirm the request.</p>
+<p>Previous versions of Synapse delegated the job of sending this email to
+an identity server. If the identity server was somehow malicious or
+became compromised, it would be theoretically possible to hijack an
+account through this means.</p>
+<p>Therefore, by default, Synapse v1.0 will send the confirmation email
+itself. If Synapse is not configured with an SMTP server, password reset
+via email will be disabled.</p>
+<p>To configure an SMTP server for Synapse, modify the configuration
+section headed <code>email</code>, and be sure to have at least the <code>smtp_host</code>,
+<code>smtp_port</code> and <code>notif_from</code> fields filled out. You may also need to set
+<code>smtp_user</code>, <code>smtp_pass</code>, and <code>require_transport_security</code>.</p>
+<p>If you are absolutely certain that you wish to continue using an
+identity server for password resets, set
+<code>trust_identity_server_for_password_resets</code> to <code>true</code>.</p>
+<p>See the <a href="usage/configuration/homeserver_sample_config.html">sample configuration file</a>
+for more details on these settings.</p>
+<h2 id="new-email-templates"><a class="header" href="#new-email-templates">New email templates</a></h2>
+<p>Some new templates have been added to the default template directory for the purpose of
+the homeserver sending its own password reset emails. If you have configured a
+custom <code>template_dir</code> in your Synapse config, these files will need to be added.</p>
+<p><code>password_reset.html</code> and <code>password_reset.txt</code> are HTML and plain text
+templates respectively that contain the contents of what will be emailed
+to the user upon attempting to reset their password via email.
+<code>password_reset_success.html</code> and <code>password_reset_failure.html</code> are HTML
+files that the content of which (assuming no redirect URL is set) will
+be shown to the user after they attempt to click the link in the email
+sent to them.</p>
+<h1 id="upgrading-to-v0990"><a class="header" href="#upgrading-to-v0990">Upgrading to v0.99.0</a></h1>
+<p>Please be aware that, before Synapse v1.0 is released around March 2019,
+you will need to replace any self-signed certificates with those
+verified by a root CA. Information on how to do so can be found at the
+ACME docs.</p>
+<h1 id="upgrading-to-v0340"><a class="header" href="#upgrading-to-v0340">Upgrading to v0.34.0</a></h1>
+<ol>
+<li>
+<p>This release is the first to fully support Python 3. Synapse will
+now run on Python versions 3.5, or 3.6 (as well as 2.7). We
+recommend switching to Python 3, as it has been shown to give
+performance improvements.</p>
+<p>For users who have installed Synapse into a virtualenv, we recommend
+doing this by creating a new virtualenv. For example:</p>
+<pre><code class="language-sh">virtualenv -p python3 ~/synapse/env3
+source ~/synapse/env3/bin/activate
+pip install matrix-synapse
+</code></pre>
+<p>You can then start synapse as normal, having activated the new
+virtualenv:</p>
+<pre><code class="language-sh">cd ~/synapse
+source env3/bin/activate
+synctl start
+</code></pre>
+<p>Users who have installed from distribution packages should see the
+relevant package documentation. See below for notes on Debian
+packages.</p>
+<ul>
+<li>
+<p>When upgrading to Python 3, you <strong>must</strong> make sure that your log
+files are configured as UTF-8, by adding <code>encoding: utf8</code> to the
+<code>RotatingFileHandler</code> configuration (if you have one) in your
+<code>&lt;server&gt;.log.config</code> file. For example, if your <code>log.config</code>
+file contains:</p>
+<pre><code class="language-yaml">handlers:
+  file:
+    class: logging.handlers.RotatingFileHandler
+    formatter: precise
+    filename: homeserver.log
+    maxBytes: 104857600
+    backupCount: 10
+    filters: [context]
+  console:
+    class: logging.StreamHandler
+    formatter: precise
+    filters: [context]
+</code></pre>
+<p>Then you should update this to be:</p>
+<pre><code class="language-yaml">handlers:
+  file:
+    class: logging.handlers.RotatingFileHandler
+    formatter: precise
+    filename: homeserver.log
+    maxBytes: 104857600
+    backupCount: 10
+    filters: [context]
+    encoding: utf8
+  console:
+    class: logging.StreamHandler
+    formatter: precise
+    filters: [context]
+</code></pre>
+<p>There is no need to revert this change if downgrading to
+Python 2.</p>
+</li>
+</ul>
+<p>We are also making available Debian packages which will run Synapse
+on Python 3. You can switch to these packages with
+<code>apt-get install matrix-synapse-py3</code>, however, please read
+<a href="https://github.com/matrix-org/synapse/blob/release-v0.34.0/debian/NEWS">debian/NEWS</a>
+before doing so. The existing <code>matrix-synapse</code> packages will
+continue to use Python 2 for the time being.</p>
+</li>
+<li>
+<p>This release removes the <code>riot.im</code> from the default list of trusted
+identity servers.</p>
+<p>If <code>riot.im</code> is in your homeserver's list of
+<code>trusted_third_party_id_servers</code>, you should remove it. It was added
+in case a hypothetical future identity server was put there. If you
+don't remove it, users may be unable to deactivate their accounts.</p>
+</li>
+<li>
+<p>This release no longer installs the (unmaintained) Matrix Console
+web client as part of the default installation. It is possible to
+re-enable it by installing it separately and setting the
+<code>web_client_location</code> config option, but please consider switching
+to another client.</p>
+</li>
+</ol>
+<h1 id="upgrading-to-v0337"><a class="header" href="#upgrading-to-v0337">Upgrading to v0.33.7</a></h1>
+<p>This release removes the example email notification templates from
+<code>res/templates</code> (they are now internal to the python package). This
+should only affect you if you (a) deploy your Synapse instance from a
+git checkout or a github snapshot URL, and (b) have email notifications
+enabled.</p>
+<p>If you have email notifications enabled, you should ensure that
+<code>email.template_dir</code> is either configured to point at a directory where
+you have installed customised templates, or leave it unset to use the
+default templates.</p>
+<h1 id="upgrading-to-v0273"><a class="header" href="#upgrading-to-v0273">Upgrading to v0.27.3</a></h1>
+<p>This release expands the anonymous usage stats sent if the opt-in
+<code>report_stats</code> configuration is set to <code>true</code>. We now capture RSS memory
+and cpu use at a very coarse level. This requires administrators to
+install the optional <code>psutil</code> python module.</p>
+<p>We would appreciate it if you could assist by ensuring this module is
+available and <code>report_stats</code> is enabled. This will let us see if
+performance changes to synapse are having an impact to the general
+community.</p>
+<h1 id="upgrading-to-v0150"><a class="header" href="#upgrading-to-v0150">Upgrading to v0.15.0</a></h1>
+<p>If you want to use the new URL previewing API
+(<code>/_matrix/media/r0/preview_url</code>) then you have to explicitly enable it
+in the config and update your dependencies dependencies. See README.rst
+for details.</p>
+<h1 id="upgrading-to-v0110"><a class="header" href="#upgrading-to-v0110">Upgrading to v0.11.0</a></h1>
+<p>This release includes the option to send anonymous usage stats to
+matrix.org, and requires that administrators explictly opt in or out by
+setting the <code>report_stats</code> option to either <code>true</code> or <code>false</code>.</p>
+<p>We would really appreciate it if you could help our project out by
+reporting anonymized usage statistics from your homeserver. Only very
+basic aggregate data (e.g. number of users) will be reported, but it
+helps us to track the growth of the Matrix community, and helps us to
+make Matrix a success, as well as to convince other networks that they
+should peer with us.</p>
+<h1 id="upgrading-to-v090"><a class="header" href="#upgrading-to-v090">Upgrading to v0.9.0</a></h1>
+<p>Application services have had a breaking API change in this version.</p>
+<p>They can no longer register themselves with a home server using the AS
+HTTP API. This decision was made because a compromised application
+service with free reign to register any regex in effect grants full
+read/write access to the home server if a regex of <code>.*</code> is used. An
+attack where a compromised AS re-registers itself with <code>.*</code> was deemed
+too big of a security risk to ignore, and so the ability to register
+with the HS remotely has been removed.</p>
+<p>It has been replaced by specifying a list of application service
+registrations in <code>homeserver.yaml</code>:</p>
+<pre><code class="language-yaml">app_service_config_files: [&quot;registration-01.yaml&quot;, &quot;registration-02.yaml&quot;]
+</code></pre>
+<p>Where <code>registration-01.yaml</code> looks like:</p>
+<pre><code class="language-yaml">url: &lt;String&gt;  # e.g. &quot;https://my.application.service.com&quot;
+as_token: &lt;String&gt;
+hs_token: &lt;String&gt;
+sender_localpart: &lt;String&gt;  # This is a new field which denotes the user_id localpart when using the AS token
+namespaces:
+  users:
+    - exclusive: &lt;Boolean&gt;
+      regex: &lt;String&gt;  # e.g. &quot;@prefix_.*&quot;
+  aliases:
+    - exclusive: &lt;Boolean&gt;
+      regex: &lt;String&gt;
+  rooms:
+    - exclusive: &lt;Boolean&gt;
+      regex: &lt;String&gt;
+</code></pre>
+<h1 id="upgrading-to-v080"><a class="header" href="#upgrading-to-v080">Upgrading to v0.8.0</a></h1>
+<p>Servers which use captchas will need to add their public key to:</p>
+<pre><code>static/client/register/register_config.js
+
+  window.matrixRegistrationConfig = {
+      recaptcha_public_key: &quot;YOUR_PUBLIC_KEY&quot;
+  };
+</code></pre>
+<p>This is required in order to support registration fallback (typically
+used on mobile devices).</p>
+<h1 id="upgrading-to-v070"><a class="header" href="#upgrading-to-v070">Upgrading to v0.7.0</a></h1>
+<p>New dependencies are:</p>
+<ul>
+<li>pydenticon</li>
+<li>simplejson</li>
+<li>syutil</li>
+<li>matrix-angular-sdk</li>
+</ul>
+<p>To pull in these dependencies in a virtual env, run:</p>
+<pre><code>python synapse/python_dependencies.py | xargs -n 1 pip install
+</code></pre>
+<h1 id="upgrading-to-v060"><a class="header" href="#upgrading-to-v060">Upgrading to v0.6.0</a></h1>
+<p>To pull in new dependencies, run:</p>
+<pre><code>python setup.py develop --user
+</code></pre>
+<p>This update includes a change to the database schema. To upgrade you
+first need to upgrade the database by running:</p>
+<pre><code>python scripts/upgrade_db_to_v0.6.0.py &lt;db&gt; &lt;server_name&gt; &lt;signing_key&gt;
+</code></pre>
+<p>Where <code>&lt;db&gt;</code> is the location of the database,
+<code>&lt;server_name&gt;</code> is the server name as specified in the
+synapse configuration, and <code>&lt;signing_key&gt;</code> is the location
+of the signing key as specified in the synapse configuration.</p>
+<p>This may take some time to complete. Failures of signatures and content
+hashes can safely be ignored.</p>
+<h1 id="upgrading-to-v051"><a class="header" href="#upgrading-to-v051">Upgrading to v0.5.1</a></h1>
+<p>Depending on precisely when you installed v0.5.0 you may have ended up
+with a stale release of the reference matrix webclient installed as a
+python module. To uninstall it and ensure you are depending on the
+latest module, please run:</p>
+<pre><code>$ pip uninstall syweb
+</code></pre>
+<h1 id="upgrading-to-v050"><a class="header" href="#upgrading-to-v050">Upgrading to v0.5.0</a></h1>
+<p>The webclient has been split out into a seperate repository/pacakage in
+this release. Before you restart your homeserver you will need to pull
+in the webclient package by running:</p>
+<pre><code>python setup.py develop --user
+</code></pre>
+<p>This release completely changes the database schema and so requires
+upgrading it before starting the new version of the homeserver.</p>
+<p>The script &quot;database-prepare-for-0.5.0.sh&quot; should be used to upgrade
+the database. This will save all user information, such as logins and
+profiles, but will otherwise purge the database. This includes messages,
+which rooms the home server was a member of and room alias mappings.</p>
+<p>If you would like to keep your history, please take a copy of your
+database file and ask for help in #matrix:matrix.org. The upgrade
+process is, unfortunately, non trivial and requires human intervention
+to resolve any resulting conflicts during the upgrade process.</p>
+<p>Before running the command the homeserver should be first completely
+shutdown. To run it, simply specify the location of the database, e.g.:</p>
+<blockquote>
+<p>./scripts/database-prepare-for-0.5.0.sh &quot;homeserver.db&quot;</p>
+</blockquote>
+<p>Once this has successfully completed it will be safe to restart the
+homeserver. You may notice that the homeserver takes a few seconds
+longer to restart than usual as it reinitializes the database.</p>
+<p>On startup of the new version, users can either rejoin remote rooms
+using room aliases or by being reinvited. Alternatively, if any other
+homeserver sends a message to a room that the homeserver was previously
+in the local HS will automatically rejoin the room.</p>
+<h1 id="upgrading-to-v040"><a class="header" href="#upgrading-to-v040">Upgrading to v0.4.0</a></h1>
+<p>This release needs an updated syutil version. Run:</p>
+<pre><code>python setup.py develop
+</code></pre>
+<p>You will also need to upgrade your configuration as the signing key
+format has changed. Run:</p>
+<pre><code>python -m synapse.app.homeserver --config-path &lt;CONFIG&gt; --generate-config
+</code></pre>
+<h1 id="upgrading-to-v030"><a class="header" href="#upgrading-to-v030">Upgrading to v0.3.0</a></h1>
+<p>This registration API now closely matches the login API. This introduces
+a bit more backwards and forwards between the HS and the client, but
+this improves the overall flexibility of the API. You can now GET on
+/register to retrieve a list of valid registration flows. Upon choosing
+one, they are submitted in the same way as login, e.g:</p>
+<pre><code>{
+  type: m.login.password,
+  user: foo,
+  password: bar
+}
+</code></pre>
+<p>The default HS supports 2 flows, with and without Identity Server email
+authentication. Enabling captcha on the HS will add in an extra step to
+all flows: <code>m.login.recaptcha</code> which must be completed before you can
+transition to the next stage. There is a new login type:
+<code>m.login.email.identity</code> which contains the <code>threepidCreds</code> key which
+were previously sent in the original register request. For more
+information on this, see the specification.</p>
+<h2 id="web-client"><a class="header" href="#web-client">Web Client</a></h2>
+<p>The VoIP specification has changed between v0.2.0 and v0.3.0. Users
+should refresh any browser tabs to get the latest web client code. Users
+on v0.2.0 of the web client will not be able to call those on v0.3.0 and
+vice versa.</p>
+<h1 id="upgrading-to-v020"><a class="header" href="#upgrading-to-v020">Upgrading to v0.2.0</a></h1>
+<p>The home server now requires setting up of SSL config before it can run.
+To automatically generate default config use:</p>
+<pre><code>$ python synapse/app/homeserver.py \
+    --server-name machine.my.domain.name \
+    --bind-port 8448 \
+    --config-path homeserver.config \
+    --generate-config
+</code></pre>
+<p>This config can be edited if desired, for example to specify a different
+SSL certificate to use. Once done you can run the home server using:</p>
+<pre><code>$ python synapse/app/homeserver.py --config-path homeserver.config
+</code></pre>
+<p>See the README.rst for more information.</p>
+<p>Also note that some config options have been renamed, including:</p>
+<ul>
+<li>&quot;host&quot; to &quot;server-name&quot;</li>
+<li>&quot;database&quot; to &quot;database-path&quot;</li>
+<li>&quot;port&quot; to &quot;bind-port&quot; and &quot;unsecure-port&quot;</li>
+</ul>
+<h1 id="upgrading-to-v001"><a class="header" href="#upgrading-to-v001">Upgrading to v0.0.1</a></h1>
+<p>This release completely changes the database schema and so requires
+upgrading it before starting the new version of the homeserver.</p>
+<p>The script &quot;database-prepare-for-0.0.1.sh&quot; should be used to upgrade
+the database. This will save all user information, such as logins and
+profiles, but will otherwise purge the database. This includes messages,
+which rooms the home server was a member of and room alias mappings.</p>
+<p>Before running the command the homeserver should be first completely
+shutdown. To run it, simply specify the location of the database, e.g.:</p>
+<blockquote>
+<p>./scripts/database-prepare-for-0.0.1.sh &quot;homeserver.db&quot;</p>
+</blockquote>
+<p>Once this has successfully completed it will be safe to restart the
+homeserver. You may notice that the homeserver takes a few seconds
+longer to restart than usual as it reinitializes the database.</p>
+<p>On startup of the new version, users can either rejoin remote rooms
+using room aliases or by being reinvited. Alternatively, if any other
+homeserver sends a message to a room that the homeserver was previously
+in the local HS will automatically rejoin the room.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="setting-up-federation"><a class="header" href="#setting-up-federation">Setting up federation</a></h1>
+<p>Federation is the process by which users on different servers can participate
+in the same room. For this to work, those other servers must be able to contact
+yours to send messages.</p>
+<p>The <code>server_name</code> configured in the Synapse configuration file (often
+<code>homeserver.yaml</code>) defines how resources (users, rooms, etc.) will be
+identified (eg: <code>@user:example.com</code>, <code>#room:example.com</code>). By default,
+it is also the domain that other servers will use to try to reach your
+server (via port 8448). This is easy to set up and will work provided
+you set the <code>server_name</code> to match your machine's public DNS hostname.</p>
+<p>For this default configuration to work, you will need to listen for TLS
+connections on port 8448. The preferred way to do that is by using a
+reverse proxy: see <a href="reverse_proxy.html">the reverse proxy documentation</a> for instructions
+on how to correctly set one up.</p>
+<p>In some cases you might not want to run Synapse on the machine that has
+the <code>server_name</code> as its public DNS hostname, or you might want federation
+traffic to use a different port than 8448. For example, you might want to
+have your user names look like <code>@user:example.com</code>, but you want to run
+Synapse on <code>synapse.example.com</code> on port 443. This can be done using
+delegation, which allows an admin to control where federation traffic should
+be sent. See <a href="delegate.html">the delegation documentation</a> for instructions on how to set this up.</p>
+<p>Once federation has been configured, you should be able to join a room over
+federation. A good place to start is <code>#synapse:matrix.org</code> - a room for
+Synapse admins.</p>
+<h2 id="troubleshooting-3"><a class="header" href="#troubleshooting-3">Troubleshooting</a></h2>
+<p>You can use the <a href="https://matrix.org/federationtester">federation tester</a>
+to check if your homeserver is configured correctly. Alternatively try the
+<a href="https://matrix.org/federationtester/api/report?server_name=DOMAIN">JSON API used by the federation tester</a>.
+Note that you'll have to modify this URL to replace <code>DOMAIN</code> with your
+<code>server_name</code>. Hitting the API directly provides extra detail.</p>
+<p>The typical failure mode for federation is that when the server tries to join
+a room, it is rejected with &quot;401: Unauthorized&quot;. Generally this means that other
+servers in the room could not access yours. (Joining a room over federation is
+a complicated dance which requires connections in both directions).</p>
+<p>Another common problem is that people on other servers can't join rooms that
+you invite them to. This can be caused by an incorrectly-configured reverse
+proxy: see <a href="reverse_proxy.html">the reverse proxy documentation</a> for instructions on how
+to correctly configure a reverse proxy.</p>
+<h3 id="known-issues"><a class="header" href="#known-issues">Known issues</a></h3>
+<p><strong>HTTP <code>308 Permanent Redirect</code> redirects are not followed</strong>: Due to missing features
+in the HTTP library used by Synapse, 308 redirects are currently not followed by
+federating servers, which can cause <code>M_UNKNOWN</code> or <code>401 Unauthorized</code> errors. This
+may affect users who are redirecting apex-to-www (e.g. <code>example.com</code> -&gt; <code>www.example.com</code>),
+and especially users of the Kubernetes <em>Nginx Ingress</em> module, which uses 308 redirect
+codes by default. For those Kubernetes users, <a href="https://stackoverflow.com/a/52617528/5096871">this Stackoverflow post</a> 
+might be helpful. For other users, switching to a <code>301 Moved Permanently</code> code may be
+an option. 308 redirect codes will be supported properly in a future
+release of Synapse.</p>
+<h2 id="running-a-demo-federation-of-synapses"><a class="header" href="#running-a-demo-federation-of-synapses">Running a demo federation of Synapses</a></h2>
+<p>If you want to get up and running quickly with a trio of homeservers in a
+private federation, there is a script in the <code>demo</code> directory. This is mainly
+useful just for development purposes. See
+<a href="https://matrix-org.github.io/synapse/develop/development/demo.html">demo scripts</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="configuration-1"><a class="header" href="#configuration-1">Configuration</a></h1>
+<p>This section contains information on tweaking Synapse via the various options in the configuration file. A configuration
+file should have been generated when you <a href="usage/configuration/../../setup/installation.html">installed Synapse</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="homeserver-sample-configuration-file"><a class="header" href="#homeserver-sample-configuration-file">Homeserver Sample Configuration File</a></h1>
+<p>Below is a sample homeserver configuration file. The homeserver configuration file 
+can be tweaked to change the behaviour of your homeserver. A restart of the server is 
+generally required to apply any changes made to this file.</p>
+<p>Note that the contents below are <em>not</em> intended to be copied and used as the basis for
+a real homeserver.yaml. Instead, if you are starting from scratch, please generate
+a fresh config using Synapse by following the instructions in
+<a href="usage/configuration/../../setup/installation.html">Installation</a>.</p>
+<pre><code class="language-yaml"># This file is maintained as an up-to-date snapshot of the default
+# homeserver.yaml configuration generated by Synapse.
+#
+# It is intended to act as a reference for the default configuration,
+# helping admins keep track of new options and other changes, and compare
+# their configs with the current default.  As such, many of the actual
+# config values shown are placeholders.
+#
+# It is *not* intended to be copied and used as the basis for a real
+# homeserver.yaml. Instead, if you are starting from scratch, please generate
+# a fresh config using Synapse by following the instructions in
+# https://matrix-org.github.io/synapse/latest/setup/installation.html.
+
+# Configuration options that take a time period can be set using a number
+# followed by a letter. Letters have the following meanings:
+# s = second
+# m = minute
+# h = hour
+# d = day
+# w = week
+# y = year
+# For example, setting redaction_retention_period: 5m would remove redacted
+# messages from the database after 5 minutes, rather than 5 months.
+
+################################################################################
+
+# Configuration file for Synapse.
+#
+# This is a YAML file: see [1] for a quick introduction. Note in particular
+# that *indentation is important*: all the elements of a list or dictionary
+# should have the same indentation.
+#
+# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
+
+
+## Modules ##
+
+# Server admins can expand Synapse's functionality with external modules.
+#
+# See https://matrix-org.github.io/synapse/latest/modules/index.html for more
+# documentation on how to configure or create custom modules for Synapse.
+#
+modules:
+  #- module: my_super_module.MySuperClass
+  #  config:
+  #    do_thing: true
+  #- module: my_other_super_module.SomeClass
+  #  config: {}
+
+
+## Server ##
+
+# The public-facing domain of the server
+#
+# The server_name name will appear at the end of usernames and room addresses
+# created on this server. For example if the server_name was example.com,
+# usernames on this server would be in the format @user:example.com
+#
+# In most cases you should avoid using a matrix specific subdomain such as
+# matrix.example.com or synapse.example.com as the server_name for the same
+# reasons you wouldn't use user@email.example.com as your email address.
+# See https://matrix-org.github.io/synapse/latest/delegate.html
+# for information on how to host Synapse on a subdomain while preserving
+# a clean server_name.
+#
+# The server_name cannot be changed later so it is important to
+# configure this correctly before you start Synapse. It should be all
+# lowercase and may contain an explicit port.
+# Examples: matrix.org, localhost:8080
+#
+server_name: &quot;SERVERNAME&quot;
+
+# When running as a daemon, the file to store the pid in
+#
+pid_file: DATADIR/homeserver.pid
+
+# The absolute URL to the web client which / will redirect to.
+#
+#web_client_location: https://riot.example.com/
+
+# The public-facing base URL that clients use to access this Homeserver (not
+# including _matrix/...). This is the same URL a user might enter into the
+# 'Custom Homeserver URL' field on their client. If you use Synapse with a
+# reverse proxy, this should be the URL to reach Synapse via the proxy.
+# Otherwise, it should be the URL to reach Synapse's client HTTP listener (see
+# 'listeners' below).
+#
+# Defaults to 'https://&lt;server_name&gt;/'.
+#
+#public_baseurl: https://example.com/
+
+# Uncomment the following to tell other servers to send federation traffic on
+# port 443.
+#
+# By default, other servers will try to reach our server on port 8448, which can
+# be inconvenient in some environments.
+#
+# Provided 'https://&lt;server_name&gt;/' on port 443 is routed to Synapse, this
+# option configures Synapse to serve a file at
+# 'https://&lt;server_name&gt;/.well-known/matrix/server'. This will tell other
+# servers to send traffic to port 443 instead.
+#
+# See https://matrix-org.github.io/synapse/latest/delegate.html for more
+# information.
+#
+# Defaults to 'false'.
+#
+#serve_server_wellknown: true
+
+# Set the soft limit on the number of file descriptors synapse can use
+# Zero is used to indicate synapse should set the soft limit to the
+# hard limit.
+#
+#soft_file_limit: 0
+
+# Presence tracking allows users to see the state (e.g online/offline)
+# of other local and remote users.
+#
+presence:
+  # Uncomment to disable presence tracking on this homeserver. This option
+  # replaces the previous top-level 'use_presence' option.
+  #
+  #enabled: false
+
+# Whether to require authentication to retrieve profile data (avatars,
+# display names) of other users through the client API. Defaults to
+# 'false'. Note that profile data is also available via the federation
+# API, unless allow_profile_lookup_over_federation is set to false.
+#
+#require_auth_for_profile_requests: true
+
+# Uncomment to require a user to share a room with another user in order
+# to retrieve their profile information. Only checked on Client-Server
+# requests. Profile requests from other servers should be checked by the
+# requesting server. Defaults to 'false'.
+#
+#limit_profile_requests_to_users_who_share_rooms: true
+
+# Uncomment to prevent a user's profile data from being retrieved and
+# displayed in a room until they have joined it. By default, a user's
+# profile data is included in an invite event, regardless of the values
+# of the above two settings, and whether or not the users share a server.
+# Defaults to 'true'.
+#
+#include_profile_data_on_invite: false
+
+# If set to 'true', removes the need for authentication to access the server's
+# public rooms directory through the client API, meaning that anyone can
+# query the room directory. Defaults to 'false'.
+#
+#allow_public_rooms_without_auth: true
+
+# If set to 'true', allows any other homeserver to fetch the server's public
+# rooms directory via federation. Defaults to 'false'.
+#
+#allow_public_rooms_over_federation: true
+
+# The default room version for newly created rooms.
+#
+# Known room versions are listed here:
+# https://spec.matrix.org/latest/rooms/#complete-list-of-room-versions
+#
+# For example, for room version 1, default_room_version should be set
+# to &quot;1&quot;.
+#
+#default_room_version: &quot;9&quot;
+
+# The GC threshold parameters to pass to `gc.set_threshold`, if defined
+#
+#gc_thresholds: [700, 10, 10]
+
+# The minimum time in seconds between each GC for a generation, regardless of
+# the GC thresholds. This ensures that we don't do GC too frequently.
+#
+# A value of `[1s, 10s, 30s]` indicates that a second must pass between consecutive
+# generation 0 GCs, etc.
+#
+# Defaults to `[1s, 10s, 30s]`.
+#
+#gc_min_interval: [0.5s, 30s, 1m]
+
+# Set the limit on the returned events in the timeline in the get
+# and sync operations. The default value is 100. -1 means no upper limit.
+#
+# Uncomment the following to increase the limit to 5000.
+#
+#filter_timeline_limit: 5000
+
+# Whether room invites to users on this server should be blocked
+# (except those sent by local server admins). The default is False.
+#
+#block_non_admin_invites: true
+
+# Room searching
+#
+# If disabled, new messages will not be indexed for searching and users
+# will receive errors when searching for messages. Defaults to enabled.
+#
+#enable_search: false
+
+# Prevent outgoing requests from being sent to the following blacklisted IP address
+# CIDR ranges. If this option is not specified then it defaults to private IP
+# address ranges (see the example below).
+#
+# The blacklist applies to the outbound requests for federation, identity servers,
+# push servers, and for checking key validity for third-party invite events.
+#
+# (0.0.0.0 and :: are always blacklisted, whether or not they are explicitly
+# listed here, since they correspond to unroutable addresses.)
+#
+# This option replaces federation_ip_range_blacklist in Synapse v1.25.0.
+#
+# Note: The value is ignored when an HTTP proxy is in use
+#
+#ip_range_blacklist:
+#  - '127.0.0.0/8'
+#  - '10.0.0.0/8'
+#  - '172.16.0.0/12'
+#  - '192.168.0.0/16'
+#  - '100.64.0.0/10'
+#  - '192.0.0.0/24'
+#  - '169.254.0.0/16'
+#  - '192.88.99.0/24'
+#  - '198.18.0.0/15'
+#  - '192.0.2.0/24'
+#  - '198.51.100.0/24'
+#  - '203.0.113.0/24'
+#  - '224.0.0.0/4'
+#  - '::1/128'
+#  - 'fe80::/10'
+#  - 'fc00::/7'
+#  - '2001:db8::/32'
+#  - 'ff00::/8'
+#  - 'fec0::/10'
+
+# List of IP address CIDR ranges that should be allowed for federation,
+# identity servers, push servers, and for checking key validity for
+# third-party invite events. This is useful for specifying exceptions to
+# wide-ranging blacklisted target IP ranges - e.g. for communication with
+# a push server only visible in your network.
+#
+# This whitelist overrides ip_range_blacklist and defaults to an empty
+# list.
+#
+#ip_range_whitelist:
+#   - '192.168.1.1'
+
+# List of ports that Synapse should listen on, their purpose and their
+# configuration.
+#
+# Options for each listener include:
+#
+#   port: the TCP port to bind to
+#
+#   bind_addresses: a list of local addresses to listen on. The default is
+#       'all local interfaces'.
+#
+#   type: the type of listener. Normally 'http', but other valid options are:
+#       'manhole' (see https://matrix-org.github.io/synapse/latest/manhole.html),
+#       'metrics' (see https://matrix-org.github.io/synapse/latest/metrics-howto.html),
+#       'replication' (see https://matrix-org.github.io/synapse/latest/workers.html).
+#
+#   tls: set to true to enable TLS for this listener. Will use the TLS
+#       key/cert specified in tls_private_key_path / tls_certificate_path.
+#
+#   x_forwarded: Only valid for an 'http' listener. Set to true to use the
+#       X-Forwarded-For header as the client IP. Useful when Synapse is
+#       behind a reverse-proxy.
+#
+#   resources: Only valid for an 'http' listener. A list of resources to host
+#       on this port. Options for each resource are:
+#
+#       names: a list of names of HTTP resources. See below for a list of
+#           valid resource names.
+#
+#       compress: set to true to enable HTTP compression for this resource.
+#
+#   additional_resources: Only valid for an 'http' listener. A map of
+#        additional endpoints which should be loaded via dynamic modules.
+#
+# Valid resource names are:
+#
+#   client: the client-server API (/_matrix/client), and the synapse admin
+#       API (/_synapse/admin). Also implies 'media' and 'static'.
+#
+#   consent: user consent forms (/_matrix/consent).
+#       See https://matrix-org.github.io/synapse/latest/consent_tracking.html.
+#
+#   federation: the server-server API (/_matrix/federation). Also implies
+#       'media', 'keys', 'openid'
+#
+#   keys: the key discovery API (/_matrix/keys).
+#
+#   media: the media API (/_matrix/media).
+#
+#   metrics: the metrics interface.
+#       See https://matrix-org.github.io/synapse/latest/metrics-howto.html.
+#
+#   openid: OpenID authentication.
+#
+#   replication: the HTTP replication API (/_synapse/replication).
+#       See https://matrix-org.github.io/synapse/latest/workers.html.
+#
+#   static: static resources under synapse/static (/_matrix/static). (Mostly
+#       useful for 'fallback authentication'.)
+#
+listeners:
+  # TLS-enabled listener: for when matrix traffic is sent directly to synapse.
+  #
+  # Disabled by default. To enable it, uncomment the following. (Note that you
+  # will also need to give Synapse a TLS key and certificate: see the TLS section
+  # below.)
+  #
+  #- port: 8448
+  #  type: http
+  #  tls: true
+  #  resources:
+  #    - names: [client, federation]
+
+  # Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy
+  # that unwraps TLS.
+  #
+  # If you plan to use a reverse proxy, please see
+  # https://matrix-org.github.io/synapse/latest/reverse_proxy.html.
+  #
+  - port: 8008
+    tls: false
+    type: http
+    x_forwarded: true
+    bind_addresses: ['::1', '127.0.0.1']
+
+    resources:
+      - names: [client, federation]
+        compress: false
+
+    # example additional_resources:
+    #
+    #additional_resources:
+    #  &quot;/_matrix/my/custom/endpoint&quot;:
+    #    module: my_module.CustomRequestHandler
+    #    config: {}
+
+  # Turn on the twisted ssh manhole service on localhost on the given
+  # port.
+  #
+  #- port: 9000
+  #  bind_addresses: ['::1', '127.0.0.1']
+  #  type: manhole
+
+# Connection settings for the manhole
+#
+manhole_settings:
+  # The username for the manhole. This defaults to 'matrix'.
+  #
+  #username: manhole
+
+  # The password for the manhole. This defaults to 'rabbithole'.
+  #
+  #password: mypassword
+
+  # The private and public SSH key pair used to encrypt the manhole traffic.
+  # If these are left unset, then hardcoded and non-secret keys are used,
+  # which could allow traffic to be intercepted if sent over a public network.
+  #
+  #ssh_priv_key_path: CONFDIR/id_rsa
+  #ssh_pub_key_path: CONFDIR/id_rsa.pub
+
+# Forward extremities can build up in a room due to networking delays between
+# homeservers. Once this happens in a large room, calculation of the state of
+# that room can become quite expensive. To mitigate this, once the number of
+# forward extremities reaches a given threshold, Synapse will send an
+# org.matrix.dummy_event event, which will reduce the forward extremities
+# in the room.
+#
+# This setting defines the threshold (i.e. number of forward extremities in the
+# room) at which dummy events are sent. The default value is 10.
+#
+#dummy_events_threshold: 5
+
+
+## Homeserver blocking ##
+
+# How to reach the server admin, used in ResourceLimitError
+#
+#admin_contact: 'mailto:admin@server.com'
+
+# Global blocking
+#
+#hs_disabled: false
+#hs_disabled_message: 'Human readable reason for why the HS is blocked'
+
+# Monthly Active User Blocking
+#
+# Used in cases where the admin or server owner wants to limit to the
+# number of monthly active users.
+#
+# 'limit_usage_by_mau' disables/enables monthly active user blocking. When
+# enabled and a limit is reached the server returns a 'ResourceLimitError'
+# with error type Codes.RESOURCE_LIMIT_EXCEEDED
+#
+# 'max_mau_value' is the hard limit of monthly active users above which
+# the server will start blocking user actions.
+#
+# 'mau_trial_days' is a means to add a grace period for active users. It
+# means that users must be active for this number of days before they
+# can be considered active and guards against the case where lots of users
+# sign up in a short space of time never to return after their initial
+# session.
+#
+# 'mau_limit_alerting' is a means of limiting client side alerting
+# should the mau limit be reached. This is useful for small instances
+# where the admin has 5 mau seats (say) for 5 specific people and no
+# interest increasing the mau limit further. Defaults to True, which
+# means that alerting is enabled
+#
+#limit_usage_by_mau: false
+#max_mau_value: 50
+#mau_trial_days: 2
+#mau_limit_alerting: false
+
+# If enabled, the metrics for the number of monthly active users will
+# be populated, however no one will be limited. If limit_usage_by_mau
+# is true, this is implied to be true.
+#
+#mau_stats_only: false
+
+# Sometimes the server admin will want to ensure certain accounts are
+# never blocked by mau checking. These accounts are specified here.
+#
+#mau_limit_reserved_threepids:
+#  - medium: 'email'
+#    address: 'reserved_user@example.com'
+
+# Used by phonehome stats to group together related servers.
+#server_context: context
+
+# Resource-constrained homeserver settings
+#
+# When this is enabled, the room &quot;complexity&quot; will be checked before a user
+# joins a new remote room. If it is above the complexity limit, the server will
+# disallow joining, or will instantly leave.
+#
+# Room complexity is an arbitrary measure based on factors such as the number of
+# users in the room.
+#
+limit_remote_rooms:
+  # Uncomment to enable room complexity checking.
+  #
+  #enabled: true
+
+  # the limit above which rooms cannot be joined. The default is 1.0.
+  #
+  #complexity: 0.5
+
+  # override the error which is returned when the room is too complex.
+  #
+  #complexity_error: &quot;This room is too complex.&quot;
+
+  # allow server admins to join complex rooms. Default is false.
+  #
+  #admins_can_join: true
+
+# Whether to require a user to be in the room to add an alias to it.
+# Defaults to 'true'.
+#
+#require_membership_for_aliases: false
+
+# Whether to allow per-room membership profiles through the send of membership
+# events with profile information that differ from the target's global profile.
+# Defaults to 'true'.
+#
+#allow_per_room_profiles: false
+
+# The largest allowed file size for a user avatar. Defaults to no restriction.
+#
+# Note that user avatar changes will not work if this is set without
+# using Synapse's media repository.
+#
+#max_avatar_size: 10M
+
+# The MIME types allowed for user avatars. Defaults to no restriction.
+#
+# Note that user avatar changes will not work if this is set without
+# using Synapse's media repository.
+#
+#allowed_avatar_mimetypes: [&quot;image/png&quot;, &quot;image/jpeg&quot;, &quot;image/gif&quot;]
+
+# How long to keep redacted events in unredacted form in the database. After
+# this period redacted events get replaced with their redacted form in the DB.
+#
+# Defaults to `7d`. Set to `null` to disable.
+#
+#redaction_retention_period: 28d
+
+# How long to track users' last seen time and IPs in the database.
+#
+# Defaults to `28d`. Set to `null` to disable clearing out of old rows.
+#
+#user_ips_max_age: 14d
+
+# Inhibits the /requestToken endpoints from returning an error that might leak
+# information about whether an e-mail address is in use or not on this
+# homeserver.
+# Note that for some endpoints the error situation is the e-mail already being
+# used, and for others the error is entering the e-mail being unused.
+# If this option is enabled, instead of returning an error, these endpoints will
+# act as if no error happened and return a fake session ID ('sid') to clients.
+#
+#request_token_inhibit_3pid_errors: true
+
+# A list of domains that the domain portion of 'next_link' parameters
+# must match.
+#
+# This parameter is optionally provided by clients while requesting
+# validation of an email or phone number, and maps to a link that
+# users will be automatically redirected to after validation
+# succeeds. Clients can make use this parameter to aid the validation
+# process.
+#
+# The whitelist is applied whether the homeserver or an
+# identity server is handling validation.
+#
+# The default value is no whitelist functionality; all domains are
+# allowed. Setting this value to an empty list will instead disallow
+# all domains.
+#
+#next_link_domain_whitelist: [&quot;matrix.org&quot;]
+
+# Templates to use when generating email or HTML page contents.
+#
+templates:
+  # Directory in which Synapse will try to find template files to use to generate
+  # email or HTML page contents.
+  # If not set, or a file is not found within the template directory, a default
+  # template from within the Synapse package will be used.
+  #
+  # See https://matrix-org.github.io/synapse/latest/templates.html for more
+  # information about using custom templates.
+  #
+  #custom_template_directory: /path/to/custom/templates/
+
+
+# Message retention policy at the server level.
+#
+# Room admins and mods can define a retention period for their rooms using the
+# 'm.room.retention' state event, and server admins can cap this period by setting
+# the 'allowed_lifetime_min' and 'allowed_lifetime_max' config options.
+#
+# If this feature is enabled, Synapse will regularly look for and purge events
+# which are older than the room's maximum retention period. Synapse will also
+# filter events received over federation so that events that should have been
+# purged are ignored and not stored again.
+#
+retention:
+  # The message retention policies feature is disabled by default. Uncomment the
+  # following line to enable it.
+  #
+  #enabled: true
+
+  # Default retention policy. If set, Synapse will apply it to rooms that lack the
+  # 'm.room.retention' state event. Currently, the value of 'min_lifetime' doesn't
+  # matter much because Synapse doesn't take it into account yet.
+  #
+  #default_policy:
+  #  min_lifetime: 1d
+  #  max_lifetime: 1y
+
+  # Retention policy limits. If set, and the state of a room contains a
+  # 'm.room.retention' event in its state which contains a 'min_lifetime' or a
+  # 'max_lifetime' that's out of these bounds, Synapse will cap the room's policy
+  # to these limits when running purge jobs.
+  #
+  #allowed_lifetime_min: 1d
+  #allowed_lifetime_max: 1y
+
+  # Server admins can define the settings of the background jobs purging the
+  # events which lifetime has expired under the 'purge_jobs' section.
+  #
+  # If no configuration is provided, a single job will be set up to delete expired
+  # events in every room daily.
+  #
+  # Each job's configuration defines which range of message lifetimes the job
+  # takes care of. For example, if 'shortest_max_lifetime' is '2d' and
+  # 'longest_max_lifetime' is '3d', the job will handle purging expired events in
+  # rooms whose state defines a 'max_lifetime' that's both higher than 2 days, and
+  # lower than or equal to 3 days. Both the minimum and the maximum value of a
+  # range are optional, e.g. a job with no 'shortest_max_lifetime' and a
+  # 'longest_max_lifetime' of '3d' will handle every room with a retention policy
+  # which 'max_lifetime' is lower than or equal to three days.
+  #
+  # The rationale for this per-job configuration is that some rooms might have a
+  # retention policy with a low 'max_lifetime', where history needs to be purged
+  # of outdated messages on a more frequent basis than for the rest of the rooms
+  # (e.g. every 12h), but not want that purge to be performed by a job that's
+  # iterating over every room it knows, which could be heavy on the server.
+  #
+  # If any purge job is configured, it is strongly recommended to have at least
+  # a single job with neither 'shortest_max_lifetime' nor 'longest_max_lifetime'
+  # set, or one job without 'shortest_max_lifetime' and one job without
+  # 'longest_max_lifetime' set. Otherwise some rooms might be ignored, even if
+  # 'allowed_lifetime_min' and 'allowed_lifetime_max' are set, because capping a
+  # room's policy to these values is done after the policies are retrieved from
+  # Synapse's database (which is done using the range specified in a purge job's
+  # configuration).
+  #
+  #purge_jobs:
+  #  - longest_max_lifetime: 3d
+  #    interval: 12h
+  #  - shortest_max_lifetime: 3d
+  #    interval: 1d
+
+
+## TLS ##
+
+# PEM-encoded X509 certificate for TLS.
+# This certificate, as of Synapse 1.0, will need to be a valid and verifiable
+# certificate, signed by a recognised Certificate Authority.
+#
+# Be sure to use a `.pem` file that includes the full certificate chain including
+# any intermediate certificates (for instance, if using certbot, use
+# `fullchain.pem` as your certificate, not `cert.pem`).
+#
+#tls_certificate_path: &quot;CONFDIR/SERVERNAME.tls.crt&quot;
+
+# PEM-encoded private key for TLS
+#
+#tls_private_key_path: &quot;CONFDIR/SERVERNAME.tls.key&quot;
+
+# Whether to verify TLS server certificates for outbound federation requests.
+#
+# Defaults to `true`. To disable certificate verification, uncomment the
+# following line.
+#
+#federation_verify_certificates: false
+
+# The minimum TLS version that will be used for outbound federation requests.
+#
+# Defaults to `1`. Configurable to `1`, `1.1`, `1.2`, or `1.3`. Note
+# that setting this value higher than `1.2` will prevent federation to most
+# of the public Matrix network: only configure it to `1.3` if you have an
+# entirely private federation setup and you can ensure TLS 1.3 support.
+#
+#federation_client_minimum_tls_version: 1.2
+
+# Skip federation certificate verification on the following whitelist
+# of domains.
+#
+# This setting should only be used in very specific cases, such as
+# federation over Tor hidden services and similar. For private networks
+# of homeservers, you likely want to use a private CA instead.
+#
+# Only effective if federation_verify_certicates is `true`.
+#
+#federation_certificate_verification_whitelist:
+#  - lon.example.com
+#  - &quot;*.domain.com&quot;
+#  - &quot;*.onion&quot;
+
+# List of custom certificate authorities for federation traffic.
+#
+# This setting should only normally be used within a private network of
+# homeservers.
+#
+# Note that this list will replace those that are provided by your
+# operating environment. Certificates must be in PEM format.
+#
+#federation_custom_ca_list:
+#  - myCA1.pem
+#  - myCA2.pem
+#  - myCA3.pem
+
+
+## Federation ##
+
+# Restrict federation to the following whitelist of domains.
+# N.B. we recommend also firewalling your federation listener to limit
+# inbound federation traffic as early as possible, rather than relying
+# purely on this application-layer restriction.  If not specified, the
+# default is to whitelist everything.
+#
+#federation_domain_whitelist:
+#  - lon.example.com
+#  - nyc.example.com
+#  - syd.example.com
+
+# Report prometheus metrics on the age of PDUs being sent to and received from
+# the following domains. This can be used to give an idea of &quot;delay&quot; on inbound
+# and outbound federation, though be aware that any delay can be due to problems
+# at either end or with the intermediate network.
+#
+# By default, no domains are monitored in this way.
+#
+#federation_metrics_domains:
+#  - matrix.org
+#  - example.com
+
+# Uncomment to disable profile lookup over federation. By default, the
+# Federation API allows other homeservers to obtain profile data of any user
+# on this homeserver. Defaults to 'true'.
+#
+#allow_profile_lookup_over_federation: false
+
+# Uncomment to disable device display name lookup over federation. By default, the
+# Federation API allows other homeservers to obtain device display names of any user
+# on this homeserver. Defaults to 'true'.
+#
+#allow_device_name_lookup_over_federation: false
+
+
+## Caching ##
+
+# Caching can be configured through the following options.
+#
+# A cache 'factor' is a multiplier that can be applied to each of
+# Synapse's caches in order to increase or decrease the maximum
+# number of entries that can be stored.
+
+# The number of events to cache in memory. Not affected by
+# caches.global_factor.
+#
+#event_cache_size: 10K
+
+caches:
+  # Controls the global cache factor, which is the default cache factor
+  # for all caches if a specific factor for that cache is not otherwise
+  # set.
+  #
+  # This can also be set by the &quot;SYNAPSE_CACHE_FACTOR&quot; environment
+  # variable. Setting by environment variable takes priority over
+  # setting through the config file.
+  #
+  # Defaults to 0.5, which will half the size of all caches.
+  #
+  #global_factor: 1.0
+
+  # A dictionary of cache name to cache factor for that individual
+  # cache. Overrides the global cache factor for a given cache.
+  #
+  # These can also be set through environment variables comprised
+  # of &quot;SYNAPSE_CACHE_FACTOR_&quot; + the name of the cache in capital
+  # letters and underscores. Setting by environment variable
+  # takes priority over setting through the config file.
+  # Ex. SYNAPSE_CACHE_FACTOR_GET_USERS_WHO_SHARE_ROOM_WITH_USER=2.0
+  #
+  # Some caches have '*' and other characters that are not
+  # alphanumeric or underscores. These caches can be named with or
+  # without the special characters stripped. For example, to specify
+  # the cache factor for `*stateGroupCache*` via an environment
+  # variable would be `SYNAPSE_CACHE_FACTOR_STATEGROUPCACHE=2.0`.
+  #
+  per_cache_factors:
+    #get_users_who_share_room_with_user: 2.0
+
+  # Controls whether cache entries are evicted after a specified time
+  # period. Defaults to true. Uncomment to disable this feature.
+  #
+  #expire_caches: false
+
+  # If expire_caches is enabled, this flag controls how long an entry can
+  # be in a cache without having been accessed before being evicted.
+  # Defaults to 30m. Uncomment to set a different time to live for cache entries.
+  #
+  #cache_entry_ttl: 30m
+
+  # Controls how long the results of a /sync request are cached for after
+  # a successful response is returned. A higher duration can help clients with
+  # intermittent connections, at the cost of higher memory usage.
+  #
+  # By default, this is zero, which means that sync responses are not cached
+  # at all.
+  #
+  #sync_response_cache_duration: 2m
+
+
+## Database ##
+
+# The 'database' setting defines the database that synapse uses to store all of
+# its data.
+#
+# 'name' gives the database engine to use: either 'sqlite3' (for SQLite) or
+# 'psycopg2' (for PostgreSQL).
+#
+# 'txn_limit' gives the maximum number of transactions to run per connection
+# before reconnecting. Defaults to 0, which means no limit.
+#
+# 'allow_unsafe_locale' is an option specific to Postgres. Under the default behavior, Synapse will refuse to
+# start if the postgres db is set to a non-C locale. You can override this behavior (which is *not* recommended)
+# by setting 'allow_unsafe_locale' to true. Note that doing so may corrupt your database. You can find more information
+# here: https://matrix-org.github.io/synapse/latest/postgres.html#fixing-incorrect-collate-or-ctype and here:
+# https://wiki.postgresql.org/wiki/Locale_data_changes
+#
+# 'args' gives options which are passed through to the database engine,
+# except for options starting 'cp_', which are used to configure the Twisted
+# connection pool. For a reference to valid arguments, see:
+#   * for sqlite: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
+#   * for postgres: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
+#   * for the connection pool: https://twistedmatrix.com/documents/current/api/twisted.enterprise.adbapi.ConnectionPool.html#__init__
+#
+#
+# Example SQLite configuration:
+#
+#database:
+#  name: sqlite3
+#  args:
+#    database: /path/to/homeserver.db
+#
+#
+# Example Postgres configuration:
+#
+#database:
+#  name: psycopg2
+#  txn_limit: 10000
+#  args:
+#    user: synapse_user
+#    password: secretpassword
+#    database: synapse
+#    host: localhost
+#    port: 5432
+#    cp_min: 5
+#    cp_max: 10
+#
+# For more information on using Synapse with Postgres,
+# see https://matrix-org.github.io/synapse/latest/postgres.html.
+#
+database:
+  name: sqlite3
+  args:
+    database: DATADIR/homeserver.db
+
+
+## Logging ##
+
+# A yaml python logging config file as described by
+# https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
+#
+log_config: &quot;CONFDIR/SERVERNAME.log.config&quot;
+
+
+## Ratelimiting ##
+
+# Ratelimiting settings for client actions (registration, login, messaging).
+#
+# Each ratelimiting configuration is made of two parameters:
+#   - per_second: number of requests a client can send per second.
+#   - burst_count: number of requests a client can send before being throttled.
+#
+# Synapse currently uses the following configurations:
+#   - one for messages that ratelimits sending based on the account the client
+#     is using
+#   - one for registration that ratelimits registration requests based on the
+#     client's IP address.
+#   - one for checking the validity of registration tokens that ratelimits
+#     requests based on the client's IP address.
+#   - one for login that ratelimits login requests based on the client's IP
+#     address.
+#   - one for login that ratelimits login requests based on the account the
+#     client is attempting to log into.
+#   - one for login that ratelimits login requests based on the account the
+#     client is attempting to log into, based on the amount of failed login
+#     attempts for this account.
+#   - one for ratelimiting redactions by room admins. If this is not explicitly
+#     set then it uses the same ratelimiting as per rc_message. This is useful
+#     to allow room admins to deal with abuse quickly.
+#   - two for ratelimiting number of rooms a user can join, &quot;local&quot; for when
+#     users are joining rooms the server is already in (this is cheap) vs
+#     &quot;remote&quot; for when users are trying to join rooms not on the server (which
+#     can be more expensive)
+#   - one for ratelimiting how often a user or IP can attempt to validate a 3PID.
+#   - two for ratelimiting how often invites can be sent in a room or to a
+#     specific user.
+#   - one for ratelimiting 3PID invites (i.e. invites sent to a third-party ID
+#     such as an email address or a phone number) based on the account that's
+#     sending the invite.
+#
+# The defaults are as shown below.
+#
+#rc_message:
+#  per_second: 0.2
+#  burst_count: 10
+#
+#rc_registration:
+#  per_second: 0.17
+#  burst_count: 3
+#
+#rc_registration_token_validity:
+#  per_second: 0.1
+#  burst_count: 5
+#
+#rc_login:
+#  address:
+#    per_second: 0.17
+#    burst_count: 3
+#  account:
+#    per_second: 0.17
+#    burst_count: 3
+#  failed_attempts:
+#    per_second: 0.17
+#    burst_count: 3
+#
+#rc_admin_redaction:
+#  per_second: 1
+#  burst_count: 50
+#
+#rc_joins:
+#  local:
+#    per_second: 0.1
+#    burst_count: 10
+#  remote:
+#    per_second: 0.01
+#    burst_count: 10
+#
+#rc_3pid_validation:
+#  per_second: 0.003
+#  burst_count: 5
+#
+#rc_invites:
+#  per_room:
+#    per_second: 0.3
+#    burst_count: 10
+#  per_user:
+#    per_second: 0.003
+#    burst_count: 5
+#
+#rc_third_party_invite:
+#  per_second: 0.2
+#  burst_count: 10
+
+# Ratelimiting settings for incoming federation
+#
+# The rc_federation configuration is made up of the following settings:
+#   - window_size: window size in milliseconds
+#   - sleep_limit: number of federation requests from a single server in
+#     a window before the server will delay processing the request.
+#   - sleep_delay: duration in milliseconds to delay processing events
+#     from remote servers by if they go over the sleep limit.
+#   - reject_limit: maximum number of concurrent federation requests
+#     allowed from a single server
+#   - concurrent: number of federation requests to concurrently process
+#     from a single server
+#
+# The defaults are as shown below.
+#
+#rc_federation:
+#  window_size: 1000
+#  sleep_limit: 10
+#  sleep_delay: 500
+#  reject_limit: 50
+#  concurrent: 3
+
+# Target outgoing federation transaction frequency for sending read-receipts,
+# per-room.
+#
+# If we end up trying to send out more read-receipts, they will get buffered up
+# into fewer transactions.
+#
+#federation_rr_transactions_per_room_per_second: 50
+
+
+
+## Media Store ##
+
+# Enable the media store service in the Synapse master. Uncomment the
+# following if you are using a separate media store worker.
+#
+#enable_media_repo: false
+
+# Directory where uploaded images and attachments are stored.
+#
+media_store_path: &quot;DATADIR/media_store&quot;
+
+# Media storage providers allow media to be stored in different
+# locations.
+#
+#media_storage_providers:
+#  - module: file_system
+#    # Whether to store newly uploaded local files
+#    store_local: false
+#    # Whether to store newly downloaded remote files
+#    store_remote: false
+#    # Whether to wait for successful storage for local uploads
+#    store_synchronous: false
+#    config:
+#       directory: /mnt/some/other/directory
+
+# The largest allowed upload size in bytes
+#
+# If you are using a reverse proxy you may also need to set this value in
+# your reverse proxy's config. Notably Nginx has a small max body size by default.
+# See https://matrix-org.github.io/synapse/latest/reverse_proxy.html.
+#
+#max_upload_size: 50M
+
+# Maximum number of pixels that will be thumbnailed
+#
+#max_image_pixels: 32M
+
+# Whether to generate new thumbnails on the fly to precisely match
+# the resolution requested by the client. If true then whenever
+# a new resolution is requested by the client the server will
+# generate a new thumbnail. If false the server will pick a thumbnail
+# from a precalculated list.
+#
+#dynamic_thumbnails: false
+
+# List of thumbnails to precalculate when an image is uploaded.
+#
+#thumbnail_sizes:
+#  - width: 32
+#    height: 32
+#    method: crop
+#  - width: 96
+#    height: 96
+#    method: crop
+#  - width: 320
+#    height: 240
+#    method: scale
+#  - width: 640
+#    height: 480
+#    method: scale
+#  - width: 800
+#    height: 600
+#    method: scale
+
+# Is the preview URL API enabled?
+#
+# 'false' by default: uncomment the following to enable it (and specify a
+# url_preview_ip_range_blacklist blacklist).
+#
+#url_preview_enabled: true
+
+# List of IP address CIDR ranges that the URL preview spider is denied
+# from accessing.  There are no defaults: you must explicitly
+# specify a list for URL previewing to work.  You should specify any
+# internal services in your network that you do not want synapse to try
+# to connect to, otherwise anyone in any Matrix room could cause your
+# synapse to issue arbitrary GET requests to your internal services,
+# causing serious security issues.
+#
+# (0.0.0.0 and :: are always blacklisted, whether or not they are explicitly
+# listed here, since they correspond to unroutable addresses.)
+#
+# This must be specified if url_preview_enabled is set. It is recommended that
+# you uncomment the following list as a starting point.
+#
+# Note: The value is ignored when an HTTP proxy is in use
+#
+#url_preview_ip_range_blacklist:
+#  - '127.0.0.0/8'
+#  - '10.0.0.0/8'
+#  - '172.16.0.0/12'
+#  - '192.168.0.0/16'
+#  - '100.64.0.0/10'
+#  - '192.0.0.0/24'
+#  - '169.254.0.0/16'
+#  - '192.88.99.0/24'
+#  - '198.18.0.0/15'
+#  - '192.0.2.0/24'
+#  - '198.51.100.0/24'
+#  - '203.0.113.0/24'
+#  - '224.0.0.0/4'
+#  - '::1/128'
+#  - 'fe80::/10'
+#  - 'fc00::/7'
+#  - '2001:db8::/32'
+#  - 'ff00::/8'
+#  - 'fec0::/10'
+
+# List of IP address CIDR ranges that the URL preview spider is allowed
+# to access even if they are specified in url_preview_ip_range_blacklist.
+# This is useful for specifying exceptions to wide-ranging blacklisted
+# target IP ranges - e.g. for enabling URL previews for a specific private
+# website only visible in your network.
+#
+#url_preview_ip_range_whitelist:
+#   - '192.168.1.1'
+
+# Optional list of URL matches that the URL preview spider is
+# denied from accessing.  You should use url_preview_ip_range_blacklist
+# in preference to this, otherwise someone could define a public DNS
+# entry that points to a private IP address and circumvent the blacklist.
+# This is more useful if you know there is an entire shape of URL that
+# you know that will never want synapse to try to spider.
+#
+# Each list entry is a dictionary of url component attributes as returned
+# by urlparse.urlsplit as applied to the absolute form of the URL.  See
+# https://docs.python.org/2/library/urlparse.html#urlparse.urlsplit
+# The values of the dictionary are treated as an filename match pattern
+# applied to that component of URLs, unless they start with a ^ in which
+# case they are treated as a regular expression match.  If all the
+# specified component matches for a given list item succeed, the URL is
+# blacklisted.
+#
+#url_preview_url_blacklist:
+#  # blacklist any URL with a username in its URI
+#  - username: '*'
+#
+#  # blacklist all *.google.com URLs
+#  - netloc: 'google.com'
+#  - netloc: '*.google.com'
+#
+#  # blacklist all plain HTTP URLs
+#  - scheme: 'http'
+#
+#  # blacklist http(s)://www.acme.com/foo
+#  - netloc: 'www.acme.com'
+#    path: '/foo'
+#
+#  # blacklist any URL with a literal IPv4 address
+#  - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+
+# The largest allowed URL preview spidering size in bytes
+#
+#max_spider_size: 10M
+
+# A list of values for the Accept-Language HTTP header used when
+# downloading webpages during URL preview generation. This allows
+# Synapse to specify the preferred languages that URL previews should
+# be in when communicating with remote servers.
+#
+# Each value is a IETF language tag; a 2-3 letter identifier for a
+# language, optionally followed by subtags separated by '-', specifying
+# a country or region variant.
+#
+# Multiple values can be provided, and a weight can be added to each by
+# using quality value syntax (;q=). '*' translates to any language.
+#
+# Defaults to &quot;en&quot;.
+#
+# Example:
+#
+# url_preview_accept_language:
+#   - en-UK
+#   - en-US;q=0.9
+#   - fr;q=0.8
+#   - *;q=0.7
+#
+url_preview_accept_language:
+#   - en
+
+
+# oEmbed allows for easier embedding content from a website. It can be
+# used for generating URLs previews of services which support it.
+#
+oembed:
+  # A default list of oEmbed providers is included with Synapse.
+  #
+  # Uncomment the following to disable using these default oEmbed URLs.
+  # Defaults to 'false'.
+  #
+  #disable_default_providers: true
+
+  # Additional files with oEmbed configuration (each should be in the
+  # form of providers.json).
+  #
+  # By default, this list is empty (so only the default providers.json
+  # is used).
+  #
+  #additional_providers:
+  #  - oembed/my_providers.json
+
+
+## Captcha ##
+# See docs/CAPTCHA_SETUP.md for full details of configuring this.
+
+# This homeserver's ReCAPTCHA public key. Must be specified if
+# enable_registration_captcha is enabled.
+#
+#recaptcha_public_key: &quot;YOUR_PUBLIC_KEY&quot;
+
+# This homeserver's ReCAPTCHA private key. Must be specified if
+# enable_registration_captcha is enabled.
+#
+#recaptcha_private_key: &quot;YOUR_PRIVATE_KEY&quot;
+
+# Uncomment to enable ReCaptcha checks when registering, preventing signup
+# unless a captcha is answered. Requires a valid ReCaptcha
+# public/private key. Defaults to 'false'.
+#
+#enable_registration_captcha: true
+
+# The API endpoint to use for verifying m.login.recaptcha responses.
+# Defaults to &quot;https://www.recaptcha.net/recaptcha/api/siteverify&quot;.
+#
+#recaptcha_siteverify_api: &quot;https://my.recaptcha.site&quot;
+
+
+## TURN ##
+
+# The public URIs of the TURN server to give to clients
+#
+#turn_uris: []
+
+# The shared secret used to compute passwords for the TURN server
+#
+#turn_shared_secret: &quot;YOUR_SHARED_SECRET&quot;
+
+# The Username and password if the TURN server needs them and
+# does not use a token
+#
+#turn_username: &quot;TURNSERVER_USERNAME&quot;
+#turn_password: &quot;TURNSERVER_PASSWORD&quot;
+
+# How long generated TURN credentials last
+#
+#turn_user_lifetime: 1h
+
+# Whether guests should be allowed to use the TURN server.
+# This defaults to True, otherwise VoIP will be unreliable for guests.
+# However, it does introduce a slight security risk as it allows users to
+# connect to arbitrary endpoints without having first signed up for a
+# valid account (e.g. by passing a CAPTCHA).
+#
+#turn_allow_guests: true
+
+
+## Registration ##
+#
+# Registration can be rate-limited using the parameters in the &quot;Ratelimiting&quot;
+# section of this file.
+
+# Enable registration for new users. Defaults to 'false'. It is highly recommended that if you enable registration,
+# you use either captcha, email, or token-based verification to verify that new users are not bots. In order to enable registration
+# without any verification, you must also set `enable_registration_without_verification`, found below.
+#
+#enable_registration: false
+
+# Enable registration without email or captcha verification. Note: this option is *not* recommended,
+# as registration without verification is a known vector for spam and abuse. Defaults to false. Has no effect
+# unless `enable_registration` is also enabled.
+#
+#enable_registration_without_verification: true
+
+# Time that a user's session remains valid for, after they log in.
+#
+# Note that this is not currently compatible with guest logins.
+#
+# Note also that this is calculated at login time: changes are not applied
+# retrospectively to users who have already logged in.
+#
+# By default, this is infinite.
+#
+#session_lifetime: 24h
+
+# Time that an access token remains valid for, if the session is
+# using refresh tokens.
+# For more information about refresh tokens, please see the manual.
+# Note that this only applies to clients which advertise support for
+# refresh tokens.
+#
+# Note also that this is calculated at login time and refresh time:
+# changes are not applied to existing sessions until they are refreshed.
+#
+# By default, this is 5 minutes.
+#
+#refreshable_access_token_lifetime: 5m
+
+# Time that a refresh token remains valid for (provided that it is not
+# exchanged for another one first).
+# This option can be used to automatically log-out inactive sessions.
+# Please see the manual for more information.
+#
+# Note also that this is calculated at login time and refresh time:
+# changes are not applied to existing sessions until they are refreshed.
+#
+# By default, this is infinite.
+#
+#refresh_token_lifetime: 24h
+
+# Time that an access token remains valid for, if the session is NOT
+# using refresh tokens.
+# Please note that not all clients support refresh tokens, so setting
+# this to a short value may be inconvenient for some users who will
+# then be logged out frequently.
+#
+# Note also that this is calculated at login time: changes are not applied
+# retrospectively to existing sessions for users that have already logged in.
+#
+# By default, this is infinite.
+#
+#nonrefreshable_access_token_lifetime: 24h
+
+# The user must provide all of the below types of 3PID when registering.
+#
+#registrations_require_3pid:
+#  - email
+#  - msisdn
+
+# Explicitly disable asking for MSISDNs from the registration
+# flow (overrides registrations_require_3pid if MSISDNs are set as required)
+#
+#disable_msisdn_registration: true
+
+# Mandate that users are only allowed to associate certain formats of
+# 3PIDs with accounts on this server.
+#
+#allowed_local_3pids:
+#  - medium: email
+#    pattern: '^[^@]+@matrix\.org$'
+#  - medium: email
+#    pattern: '^[^@]+@vector\.im$'
+#  - medium: msisdn
+#    pattern: '\+44'
+
+# Enable 3PIDs lookup requests to identity servers from this server.
+#
+#enable_3pid_lookup: true
+
+# Require users to submit a token during registration.
+# Tokens can be managed using the admin API:
+# https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/registration_tokens.html
+# Note that `enable_registration` must be set to `true`.
+# Disabling this option will not delete any tokens previously generated.
+# Defaults to false. Uncomment the following to require tokens:
+#
+#registration_requires_token: true
+
+# If set, allows registration of standard or admin accounts by anyone who
+# has the shared secret, even if registration is otherwise disabled.
+#
+#registration_shared_secret: &lt;PRIVATE STRING&gt;
+
+# Set the number of bcrypt rounds used to generate password hash.
+# Larger numbers increase the work factor needed to generate the hash.
+# The default number is 12 (which equates to 2^12 rounds).
+# N.B. that increasing this will exponentially increase the time required
+# to register or login - e.g. 24 =&gt; 2^24 rounds which will take &gt;20 mins.
+#
+#bcrypt_rounds: 12
+
+# Allows users to register as guests without a password/email/etc, and
+# participate in rooms hosted on this server which have been made
+# accessible to anonymous users.
+#
+#allow_guest_access: false
+
+# The identity server which we suggest that clients should use when users log
+# in on this server.
+#
+# (By default, no suggestion is made, so it is left up to the client.
+# This setting is ignored unless public_baseurl is also explicitly set.)
+#
+#default_identity_server: https://matrix.org
+
+# Handle threepid (email/phone etc) registration and password resets through a set of
+# *trusted* identity servers. Note that this allows the configured identity server to
+# reset passwords for accounts!
+#
+# Be aware that if `email` is not set, and SMTP options have not been
+# configured in the email config block, registration and user password resets via
+# email will be globally disabled.
+#
+# Additionally, if `msisdn` is not set, registration and password resets via msisdn
+# will be disabled regardless, and users will not be able to associate an msisdn
+# identifier to their account. This is due to Synapse currently not supporting
+# any method of sending SMS messages on its own.
+#
+# To enable using an identity server for operations regarding a particular third-party
+# identifier type, set the value to the URL of that identity server as shown in the
+# examples below.
+#
+# Servers handling the these requests must answer the `/requestToken` endpoints defined
+# by the Matrix Identity Service API specification:
+# https://matrix.org/docs/spec/identity_service/latest
+#
+account_threepid_delegates:
+    #email: https://example.com     # Delegate email sending to example.com
+    #msisdn: http://localhost:8090  # Delegate SMS sending to this local process
+
+# Whether users are allowed to change their displayname after it has
+# been initially set. Useful when provisioning users based on the
+# contents of a third-party directory.
+#
+# Does not apply to server administrators. Defaults to 'true'
+#
+#enable_set_displayname: false
+
+# Whether users are allowed to change their avatar after it has been
+# initially set. Useful when provisioning users based on the contents
+# of a third-party directory.
+#
+# Does not apply to server administrators. Defaults to 'true'
+#
+#enable_set_avatar_url: false
+
+# Whether users can change the 3PIDs associated with their accounts
+# (email address and msisdn).
+#
+# Defaults to 'true'
+#
+#enable_3pid_changes: false
+
+# Users who register on this homeserver will automatically be joined
+# to these rooms.
+#
+# By default, any room aliases included in this list will be created
+# as a publicly joinable room when the first user registers for the
+# homeserver. This behaviour can be customised with the settings below.
+# If the room already exists, make certain it is a publicly joinable
+# room. The join rule of the room must be set to 'public'.
+#
+#auto_join_rooms:
+#  - &quot;#example:example.com&quot;
+
+# Where auto_join_rooms are specified, setting this flag ensures that the
+# the rooms exist by creating them when the first user on the
+# homeserver registers.
+#
+# By default the auto-created rooms are publicly joinable from any federated
+# server. Use the autocreate_auto_join_rooms_federated and
+# autocreate_auto_join_room_preset settings below to customise this behaviour.
+#
+# Setting to false means that if the rooms are not manually created,
+# users cannot be auto-joined since they do not exist.
+#
+# Defaults to true. Uncomment the following line to disable automatically
+# creating auto-join rooms.
+#
+#autocreate_auto_join_rooms: false
+
+# Whether the auto_join_rooms that are auto-created are available via
+# federation. Only has an effect if autocreate_auto_join_rooms is true.
+#
+# Note that whether a room is federated cannot be modified after
+# creation.
+#
+# Defaults to true: the room will be joinable from other servers.
+# Uncomment the following to prevent users from other homeservers from
+# joining these rooms.
+#
+#autocreate_auto_join_rooms_federated: false
+
+# The room preset to use when auto-creating one of auto_join_rooms. Only has an
+# effect if autocreate_auto_join_rooms is true.
+#
+# This can be one of &quot;public_chat&quot;, &quot;private_chat&quot;, or &quot;trusted_private_chat&quot;.
+# If a value of &quot;private_chat&quot; or &quot;trusted_private_chat&quot; is used then
+# auto_join_mxid_localpart must also be configured.
+#
+# Defaults to &quot;public_chat&quot;, meaning that the room is joinable by anyone, including
+# federated servers if autocreate_auto_join_rooms_federated is true (the default).
+# Uncomment the following to require an invitation to join these rooms.
+#
+#autocreate_auto_join_room_preset: private_chat
+
+# The local part of the user id which is used to create auto_join_rooms if
+# autocreate_auto_join_rooms is true. If this is not provided then the
+# initial user account that registers will be used to create the rooms.
+#
+# The user id is also used to invite new users to any auto-join rooms which
+# are set to invite-only.
+#
+# It *must* be configured if autocreate_auto_join_room_preset is set to
+# &quot;private_chat&quot; or &quot;trusted_private_chat&quot;.
+#
+# Note that this must be specified in order for new users to be correctly
+# invited to any auto-join rooms which have been set to invite-only (either
+# at the time of creation or subsequently).
+#
+# Note that, if the room already exists, this user must be joined and
+# have the appropriate permissions to invite new members.
+#
+#auto_join_mxid_localpart: system
+
+# When auto_join_rooms is specified, setting this flag to false prevents
+# guest accounts from being automatically joined to the rooms.
+#
+# Defaults to true.
+#
+#auto_join_rooms_for_guests: false
+
+# Whether to inhibit errors raised when registering a new account if the user ID
+# already exists. If turned on, that requests to /register/available will always
+# show a user ID as available, and Synapse won't raise an error when starting
+# a registration with a user ID that already exists. However, Synapse will still
+# raise an error if the registration completes and the username conflicts.
+#
+# Defaults to false.
+#
+#inhibit_user_in_use_error: true
+
+
+## Metrics ###
+
+# Enable collection and rendering of performance metrics
+#
+#enable_metrics: false
+
+# Enable sentry integration
+# NOTE: While attempts are made to ensure that the logs don't contain
+# any sensitive information, this cannot be guaranteed. By enabling
+# this option the sentry server may therefore receive sensitive
+# information, and it in turn may then diseminate sensitive information
+# through insecure notification channels if so configured.
+#
+#sentry:
+#    dsn: &quot;...&quot;
+
+# Flags to enable Prometheus metrics which are not suitable to be
+# enabled by default, either for performance reasons or limited use.
+#
+metrics_flags:
+    # Publish synapse_federation_known_servers, a gauge of the number of
+    # servers this homeserver knows about, including itself. May cause
+    # performance problems on large homeservers.
+    #
+    #known_servers: true
+
+# Whether or not to report anonymized homeserver usage statistics.
+#
+#report_stats: true|false
+
+# The endpoint to report the anonymized homeserver usage statistics to.
+# Defaults to https://matrix.org/report-usage-stats/push
+#
+#report_stats_endpoint: https://example.com/report-usage-stats/push
+
+
+## API Configuration ##
+
+# Controls for the state that is shared with users who receive an invite
+# to a room
+#
+room_prejoin_state:
+   # By default, the following state event types are shared with users who
+   # receive invites to the room:
+   #
+   # - m.room.join_rules
+   # - m.room.canonical_alias
+   # - m.room.avatar
+   # - m.room.encryption
+   # - m.room.name
+   # - m.room.create
+   # - m.room.topic
+   #
+   # Uncomment the following to disable these defaults (so that only the event
+   # types listed in 'additional_event_types' are shared). Defaults to 'false'.
+   #
+   #disable_default_event_types: true
+
+   # Additional state event types to share with users when they are invited
+   # to a room.
+   #
+   # By default, this list is empty (so only the default event types are shared).
+   #
+   #additional_event_types:
+   #  - org.example.custom.event.type
+
+# We record the IP address of clients used to access the API for various
+# reasons, including displaying it to the user in the &quot;Where you're signed in&quot;
+# dialog.
+#
+# By default, when puppeting another user via the admin API, the client IP
+# address is recorded against the user who created the access token (ie, the
+# admin user), and *not* the puppeted user.
+#
+# Uncomment the following to also record the IP address against the puppeted
+# user. (This also means that the puppeted user will count as an &quot;active&quot; user
+# for the purpose of monthly active user tracking - see 'limit_usage_by_mau' etc
+# above.)
+#
+#track_puppeted_user_ips: true
+
+
+# A list of application service config files to use
+#
+#app_service_config_files:
+#  - app_service_1.yaml
+#  - app_service_2.yaml
+
+# Uncomment to enable tracking of application service IP addresses. Implicitly
+# enables MAU tracking for application service users.
+#
+#track_appservice_user_ips: true
+
+
+# a secret which is used to sign access tokens. If none is specified,
+# the registration_shared_secret is used, if one is given; otherwise,
+# a secret key is derived from the signing key.
+#
+#macaroon_secret_key: &lt;PRIVATE STRING&gt;
+
+# a secret which is used to calculate HMACs for form values, to stop
+# falsification of values. Must be specified for the User Consent
+# forms to work.
+#
+#form_secret: &lt;PRIVATE STRING&gt;
+
+## Signing Keys ##
+
+# Path to the signing key to sign messages with
+#
+signing_key_path: &quot;CONFDIR/SERVERNAME.signing.key&quot;
+
+# The keys that the server used to sign messages with but won't use
+# to sign new messages.
+#
+old_signing_keys:
+  # For each key, `key` should be the base64-encoded public key, and
+  # `expired_ts`should be the time (in milliseconds since the unix epoch) that
+  # it was last used.
+  #
+  # It is possible to build an entry from an old signing.key file using the
+  # `export_signing_key` script which is provided with synapse.
+  #
+  # For example:
+  #
+  #&quot;ed25519:id&quot;: { key: &quot;base64string&quot;, expired_ts: 123456789123 }
+
+# How long key response published by this server is valid for.
+# Used to set the valid_until_ts in /key/v2 APIs.
+# Determines how quickly servers will query to check which keys
+# are still valid.
+#
+#key_refresh_interval: 1d
+
+# The trusted servers to download signing keys from.
+#
+# When we need to fetch a signing key, each server is tried in parallel.
+#
+# Normally, the connection to the key server is validated via TLS certificates.
+# Additional security can be provided by configuring a `verify key`, which
+# will make synapse check that the response is signed by that key.
+#
+# This setting supercedes an older setting named `perspectives`. The old format
+# is still supported for backwards-compatibility, but it is deprecated.
+#
+# 'trusted_key_servers' defaults to matrix.org, but using it will generate a
+# warning on start-up. To suppress this warning, set
+# 'suppress_key_server_warning' to true.
+#
+# Options for each entry in the list include:
+#
+#    server_name: the name of the server. required.
+#
+#    verify_keys: an optional map from key id to base64-encoded public key.
+#       If specified, we will check that the response is signed by at least
+#       one of the given keys.
+#
+#    accept_keys_insecurely: a boolean. Normally, if `verify_keys` is unset,
+#       and federation_verify_certificates is not `true`, synapse will refuse
+#       to start, because this would allow anyone who can spoof DNS responses
+#       to masquerade as the trusted key server. If you know what you are doing
+#       and are sure that your network environment provides a secure connection
+#       to the key server, you can set this to `true` to override this
+#       behaviour.
+#
+# An example configuration might look like:
+#
+#trusted_key_servers:
+#  - server_name: &quot;my_trusted_server.example.com&quot;
+#    verify_keys:
+#      &quot;ed25519:auto&quot;: &quot;abcdefghijklmnopqrstuvwxyzabcdefghijklmopqr&quot;
+#  - server_name: &quot;my_other_trusted_server.example.com&quot;
+#
+trusted_key_servers:
+  - server_name: &quot;matrix.org&quot;
+
+# Uncomment the following to disable the warning that is emitted when the
+# trusted_key_servers include 'matrix.org'. See above.
+#
+#suppress_key_server_warning: true
+
+# The signing keys to use when acting as a trusted key server. If not specified
+# defaults to the server signing key.
+#
+# Can contain multiple keys, one per line.
+#
+#key_server_signing_keys_path: &quot;key_server_signing_keys.key&quot;
+
+
+## Single sign-on integration ##
+
+# The following settings can be used to make Synapse use a single sign-on
+# provider for authentication, instead of its internal password database.
+#
+# You will probably also want to set the following options to `false` to
+# disable the regular login/registration flows:
+#   * enable_registration
+#   * password_config.enabled
+#
+# You will also want to investigate the settings under the &quot;sso&quot; configuration
+# section below.
+
+# Enable SAML2 for registration and login. Uses pysaml2.
+#
+# At least one of `sp_config` or `config_path` must be set in this section to
+# enable SAML login.
+#
+# Once SAML support is enabled, a metadata file will be exposed at
+# https://&lt;server&gt;:&lt;port&gt;/_synapse/client/saml2/metadata.xml, which you may be able to
+# use to configure your SAML IdP with. Alternatively, you can manually configure
+# the IdP to use an ACS location of
+# https://&lt;server&gt;:&lt;port&gt;/_synapse/client/saml2/authn_response.
+#
+saml2_config:
+  # `sp_config` is the configuration for the pysaml2 Service Provider.
+  # See pysaml2 docs for format of config.
+  #
+  # Default values will be used for the 'entityid' and 'service' settings,
+  # so it is not normally necessary to specify them unless you need to
+  # override them.
+  #
+  sp_config:
+    # Point this to the IdP's metadata. You must provide either a local
+    # file via the `local` attribute or (preferably) a URL via the
+    # `remote` attribute.
+    #
+    #metadata:
+    #  local: [&quot;saml2/idp.xml&quot;]
+    #  remote:
+    #    - url: https://our_idp/metadata.xml
+
+    # Allowed clock difference in seconds between the homeserver and IdP.
+    #
+    # Uncomment the below to increase the accepted time difference from 0 to 3 seconds.
+    #
+    #accepted_time_diff: 3
+
+    # By default, the user has to go to our login page first. If you'd like
+    # to allow IdP-initiated login, set 'allow_unsolicited: true' in a
+    # 'service.sp' section:
+    #
+    #service:
+    #  sp:
+    #    allow_unsolicited: true
+
+    # The examples below are just used to generate our metadata xml, and you
+    # may well not need them, depending on your setup. Alternatively you
+    # may need a whole lot more detail - see the pysaml2 docs!
+
+    #description: [&quot;My awesome SP&quot;, &quot;en&quot;]
+    #name: [&quot;Test SP&quot;, &quot;en&quot;]
+
+    #ui_info:
+    #  display_name:
+    #    - lang: en
+    #      text: &quot;Display Name is the descriptive name of your service.&quot;
+    #  description:
+    #    - lang: en
+    #      text: &quot;Description should be a short paragraph explaining the purpose of the service.&quot;
+    #  information_url:
+    #    - lang: en
+    #      text: &quot;https://example.com/terms-of-service&quot;
+    #  privacy_statement_url:
+    #    - lang: en
+    #      text: &quot;https://example.com/privacy-policy&quot;
+    #  keywords:
+    #    - lang: en
+    #      text: [&quot;Matrix&quot;, &quot;Element&quot;]
+    #  logo:
+    #    - lang: en
+    #      text: &quot;https://example.com/logo.svg&quot;
+    #      width: &quot;200&quot;
+    #      height: &quot;80&quot;
+
+    #organization:
+    #  name: Example com
+    #  display_name:
+    #    - [&quot;Example co&quot;, &quot;en&quot;]
+    #  url: &quot;http://example.com&quot;
+
+    #contact_person:
+    #  - given_name: Bob
+    #    sur_name: &quot;the Sysadmin&quot;
+    #    email_address&quot;: [&quot;admin@example.com&quot;]
+    #    contact_type&quot;: technical
+
+  # Instead of putting the config inline as above, you can specify a
+  # separate pysaml2 configuration file:
+  #
+  #config_path: &quot;CONFDIR/sp_conf.py&quot;
+
+  # The lifetime of a SAML session. This defines how long a user has to
+  # complete the authentication process, if allow_unsolicited is unset.
+  # The default is 15 minutes.
+  #
+  #saml_session_lifetime: 5m
+
+  # An external module can be provided here as a custom solution to
+  # mapping attributes returned from a saml provider onto a matrix user.
+  #
+  user_mapping_provider:
+    # The custom module's class. Uncomment to use a custom module.
+    #
+    #module: mapping_provider.SamlMappingProvider
+
+    # Custom configuration values for the module. Below options are
+    # intended for the built-in provider, they should be changed if
+    # using a custom module. This section will be passed as a Python
+    # dictionary to the module's `parse_config` method.
+    #
+    config:
+      # The SAML attribute (after mapping via the attribute maps) to use
+      # to derive the Matrix ID from. 'uid' by default.
+      #
+      # Note: This used to be configured by the
+      # saml2_config.mxid_source_attribute option. If that is still
+      # defined, its value will be used instead.
+      #
+      #mxid_source_attribute: displayName
+
+      # The mapping system to use for mapping the saml attribute onto a
+      # matrix ID.
+      #
+      # Options include:
+      #  * 'hexencode' (which maps unpermitted characters to '=xx')
+      #  * 'dotreplace' (which replaces unpermitted characters with
+      #     '.').
+      # The default is 'hexencode'.
+      #
+      # Note: This used to be configured by the
+      # saml2_config.mxid_mapping option. If that is still defined, its
+      # value will be used instead.
+      #
+      #mxid_mapping: dotreplace
+
+  # In previous versions of synapse, the mapping from SAML attribute to
+  # MXID was always calculated dynamically rather than stored in a
+  # table. For backwards- compatibility, we will look for user_ids
+  # matching such a pattern before creating a new account.
+  #
+  # This setting controls the SAML attribute which will be used for this
+  # backwards-compatibility lookup. Typically it should be 'uid', but if
+  # the attribute maps are changed, it may be necessary to change it.
+  #
+  # The default is 'uid'.
+  #
+  #grandfathered_mxid_source_attribute: upn
+
+  # It is possible to configure Synapse to only allow logins if SAML attributes
+  # match particular values. The requirements can be listed under
+  # `attribute_requirements` as shown below. All of the listed attributes must
+  # match for the login to be permitted.
+  #
+  #attribute_requirements:
+  #  - attribute: userGroup
+  #    value: &quot;staff&quot;
+  #  - attribute: department
+  #    value: &quot;sales&quot;
+
+  # If the metadata XML contains multiple IdP entities then the `idp_entityid`
+  # option must be set to the entity to redirect users to.
+  #
+  # Most deployments only have a single IdP entity and so should omit this
+  # option.
+  #
+  #idp_entityid: 'https://our_idp/entityid'
+
+
+# List of OpenID Connect (OIDC) / OAuth 2.0 identity providers, for registration
+# and login.
+#
+# Options for each entry include:
+#
+#   idp_id: a unique identifier for this identity provider. Used internally
+#       by Synapse; should be a single word such as 'github'.
+#
+#       Note that, if this is changed, users authenticating via that provider
+#       will no longer be recognised as the same user!
+#
+#       (Use &quot;oidc&quot; here if you are migrating from an old &quot;oidc_config&quot;
+#       configuration.)
+#
+#   idp_name: A user-facing name for this identity provider, which is used to
+#       offer the user a choice of login mechanisms.
+#
+#   idp_icon: An optional icon for this identity provider, which is presented
+#       by clients and Synapse's own IdP picker page. If given, must be an
+#       MXC URI of the format mxc://&lt;server-name&gt;/&lt;media-id&gt;. (An easy way to
+#       obtain such an MXC URI is to upload an image to an (unencrypted) room
+#       and then copy the &quot;url&quot; from the source of the event.)
+#
+#   idp_brand: An optional brand for this identity provider, allowing clients
+#       to style the login flow according to the identity provider in question.
+#       See the spec for possible options here.
+#
+#   discover: set to 'false' to disable the use of the OIDC discovery mechanism
+#       to discover endpoints. Defaults to true.
+#
+#   issuer: Required. The OIDC issuer. Used to validate tokens and (if discovery
+#       is enabled) to discover the provider's endpoints.
+#
+#   client_id: Required. oauth2 client id to use.
+#
+#   client_secret: oauth2 client secret to use. May be omitted if
+#        client_secret_jwt_key is given, or if client_auth_method is 'none'.
+#
+#   client_secret_jwt_key: Alternative to client_secret: details of a key used
+#      to create a JSON Web Token to be used as an OAuth2 client secret. If
+#      given, must be a dictionary with the following properties:
+#
+#          key: a pem-encoded signing key. Must be a suitable key for the
+#              algorithm specified. Required unless 'key_file' is given.
+#
+#          key_file: the path to file containing a pem-encoded signing key file.
+#              Required unless 'key' is given.
+#
+#          jwt_header: a dictionary giving properties to include in the JWT
+#              header. Must include the key 'alg', giving the algorithm used to
+#              sign the JWT, such as &quot;ES256&quot;, using the JWA identifiers in
+#              RFC7518.
+#
+#          jwt_payload: an optional dictionary giving properties to include in
+#              the JWT payload. Normally this should include an 'iss' key.
+#
+#   client_auth_method: auth method to use when exchanging the token. Valid
+#       values are 'client_secret_basic' (default), 'client_secret_post' and
+#       'none'.
+#
+#   scopes: list of scopes to request. This should normally include the &quot;openid&quot;
+#       scope. Defaults to [&quot;openid&quot;].
+#
+#   authorization_endpoint: the oauth2 authorization endpoint. Required if
+#       provider discovery is disabled.
+#
+#   token_endpoint: the oauth2 token endpoint. Required if provider discovery is
+#       disabled.
+#
+#   userinfo_endpoint: the OIDC userinfo endpoint. Required if discovery is
+#       disabled and the 'openid' scope is not requested.
+#
+#   jwks_uri: URI where to fetch the JWKS. Required if discovery is disabled and
+#       the 'openid' scope is used.
+#
+#   skip_verification: set to 'true' to skip metadata verification. Use this if
+#       you are connecting to a provider that is not OpenID Connect compliant.
+#       Defaults to false. Avoid this in production.
+#
+#   user_profile_method: Whether to fetch the user profile from the userinfo
+#       endpoint, or to rely on the data returned in the id_token from the
+#       token_endpoint.
+#
+#       Valid values are: 'auto' or 'userinfo_endpoint'.
+#
+#       Defaults to 'auto', which uses the userinfo endpoint if 'openid' is
+#       not included in 'scopes'. Set to 'userinfo_endpoint' to always use the
+#       userinfo endpoint.
+#
+#   allow_existing_users: set to 'true' to allow a user logging in via OIDC to
+#       match a pre-existing account instead of failing. This could be used if
+#       switching from password logins to OIDC. Defaults to false.
+#
+#   user_mapping_provider: Configuration for how attributes returned from a OIDC
+#       provider are mapped onto a matrix user. This setting has the following
+#       sub-properties:
+#
+#       module: The class name of a custom mapping module. Default is
+#           'synapse.handlers.oidc.JinjaOidcMappingProvider'.
+#           See https://matrix-org.github.io/synapse/latest/sso_mapping_providers.html#openid-mapping-providers
+#           for information on implementing a custom mapping provider.
+#
+#       config: Configuration for the mapping provider module. This section will
+#           be passed as a Python dictionary to the user mapping provider
+#           module's `parse_config` method.
+#
+#           For the default provider, the following settings are available:
+#
+#             subject_claim: name of the claim containing a unique identifier
+#                 for the user. Defaults to 'sub', which OpenID Connect
+#                 compliant providers should provide.
+#
+#             localpart_template: Jinja2 template for the localpart of the MXID.
+#                 If this is not set, the user will be prompted to choose their
+#                 own username (see the documentation for the
+#                 'sso_auth_account_details.html' template). This template can
+#                 use the 'localpart_from_email' filter.
+#
+#             confirm_localpart: Whether to prompt the user to validate (or
+#                 change) the generated localpart (see the documentation for the
+#                 'sso_auth_account_details.html' template), instead of
+#                 registering the account right away.
+#
+#             display_name_template: Jinja2 template for the display name to set
+#                 on first login. If unset, no displayname will be set.
+#
+#             email_template: Jinja2 template for the email address of the user.
+#                 If unset, no email address will be added to the account.
+#
+#             extra_attributes: a map of Jinja2 templates for extra attributes
+#                 to send back to the client during login.
+#                 Note that these are non-standard and clients will ignore them
+#                 without modifications.
+#
+#           When rendering, the Jinja2 templates are given a 'user' variable,
+#           which is set to the claims returned by the UserInfo Endpoint and/or
+#           in the ID Token.
+#
+#   It is possible to configure Synapse to only allow logins if certain attributes
+#   match particular values in the OIDC userinfo. The requirements can be listed under
+#   `attribute_requirements` as shown below. All of the listed attributes must
+#   match for the login to be permitted. Additional attributes can be added to
+#   userinfo by expanding the `scopes` section of the OIDC config to retrieve
+#   additional information from the OIDC provider.
+#
+#   If the OIDC claim is a list, then the attribute must match any value in the list.
+#   Otherwise, it must exactly match the value of the claim. Using the example
+#   below, the `family_name` claim MUST be &quot;Stephensson&quot;, but the `groups`
+#   claim MUST contain &quot;admin&quot;.
+#
+#   attribute_requirements:
+#     - attribute: family_name
+#       value: &quot;Stephensson&quot;
+#     - attribute: groups
+#       value: &quot;admin&quot;
+#
+# See https://matrix-org.github.io/synapse/latest/openid.html
+# for information on how to configure these options.
+#
+# For backwards compatibility, it is also possible to configure a single OIDC
+# provider via an 'oidc_config' setting. This is now deprecated and admins are
+# advised to migrate to the 'oidc_providers' format. (When doing that migration,
+# use 'oidc' for the idp_id to ensure that existing users continue to be
+# recognised.)
+#
+oidc_providers:
+  # Generic example
+  #
+  #- idp_id: my_idp
+  #  idp_name: &quot;My OpenID provider&quot;
+  #  idp_icon: &quot;mxc://example.com/mediaid&quot;
+  #  discover: false
+  #  issuer: &quot;https://accounts.example.com/&quot;
+  #  client_id: &quot;provided-by-your-issuer&quot;
+  #  client_secret: &quot;provided-by-your-issuer&quot;
+  #  client_auth_method: client_secret_post
+  #  scopes: [&quot;openid&quot;, &quot;profile&quot;]
+  #  authorization_endpoint: &quot;https://accounts.example.com/oauth2/auth&quot;
+  #  token_endpoint: &quot;https://accounts.example.com/oauth2/token&quot;
+  #  userinfo_endpoint: &quot;https://accounts.example.com/userinfo&quot;
+  #  jwks_uri: &quot;https://accounts.example.com/.well-known/jwks.json&quot;
+  #  skip_verification: true
+  #  user_mapping_provider:
+  #    config:
+  #      subject_claim: &quot;id&quot;
+  #      localpart_template: &quot;{{ user.login }}&quot;
+  #      display_name_template: &quot;{{ user.name }}&quot;
+  #      email_template: &quot;{{ user.email }}&quot;
+  #  attribute_requirements:
+  #    - attribute: userGroup
+  #      value: &quot;synapseUsers&quot;
+
+
+# Enable Central Authentication Service (CAS) for registration and login.
+#
+cas_config:
+  # Uncomment the following to enable authorization against a CAS server.
+  # Defaults to false.
+  #
+  #enabled: true
+
+  # The URL of the CAS authorization endpoint.
+  #
+  #server_url: &quot;https://cas-server.com&quot;
+
+  # The attribute of the CAS response to use as the display name.
+  #
+  # If unset, no displayname will be set.
+  #
+  #displayname_attribute: name
+
+  # It is possible to configure Synapse to only allow logins if CAS attributes
+  # match particular values. All of the keys in the mapping below must exist
+  # and the values must match the given value. Alternately if the given value
+  # is None then any value is allowed (the attribute just must exist).
+  # All of the listed attributes must match for the login to be permitted.
+  #
+  #required_attributes:
+  #  userGroup: &quot;staff&quot;
+  #  department: None
+
+
+# Additional settings to use with single-sign on systems such as OpenID Connect,
+# SAML2 and CAS.
+#
+# Server admins can configure custom templates for pages related to SSO. See
+# https://matrix-org.github.io/synapse/latest/templates.html for more information.
+#
+sso:
+    # A list of client URLs which are whitelisted so that the user does not
+    # have to confirm giving access to their account to the URL. Any client
+    # whose URL starts with an entry in the following list will not be subject
+    # to an additional confirmation step after the SSO login is completed.
+    #
+    # WARNING: An entry such as &quot;https://my.client&quot; is insecure, because it
+    # will also match &quot;https://my.client.evil.site&quot;, exposing your users to
+    # phishing attacks from evil.site. To avoid this, include a slash after the
+    # hostname: &quot;https://my.client/&quot;.
+    #
+    # The login fallback page (used by clients that don't natively support the
+    # required login flows) is whitelisted in addition to any URLs in this list.
+    #
+    # By default, this list contains only the login fallback page.
+    #
+    #client_whitelist:
+    #  - https://riot.im/develop
+    #  - https://my.custom.client/
+
+    # Uncomment to keep a user's profile fields in sync with information from
+    # the identity provider. Currently only syncing the displayname is
+    # supported. Fields are checked on every SSO login, and are updated
+    # if necessary.
+    #
+    # Note that enabling this option will override user profile information,
+    # regardless of whether users have opted-out of syncing that
+    # information when first signing in. Defaults to false.
+    #
+    #update_profile_information: true
+
+
+# JSON web token integration. The following settings can be used to make
+# Synapse JSON web tokens for authentication, instead of its internal
+# password database.
+#
+# Each JSON Web Token needs to contain a &quot;sub&quot; (subject) claim, which is
+# used as the localpart of the mxid.
+#
+# Additionally, the expiration time (&quot;exp&quot;), not before time (&quot;nbf&quot;),
+# and issued at (&quot;iat&quot;) claims are validated if present.
+#
+# Note that this is a non-standard login type and client support is
+# expected to be non-existent.
+#
+# See https://matrix-org.github.io/synapse/latest/jwt.html.
+#
+#jwt_config:
+    # Uncomment the following to enable authorization using JSON web
+    # tokens. Defaults to false.
+    #
+    #enabled: true
+
+    # This is either the private shared secret or the public key used to
+    # decode the contents of the JSON web token.
+    #
+    # Required if 'enabled' is true.
+    #
+    #secret: &quot;provided-by-your-issuer&quot;
+
+    # The algorithm used to sign the JSON web token.
+    #
+    # Supported algorithms are listed at
+    # https://pyjwt.readthedocs.io/en/latest/algorithms.html
+    #
+    # Required if 'enabled' is true.
+    #
+    #algorithm: &quot;provided-by-your-issuer&quot;
+
+    # Name of the claim containing a unique identifier for the user.
+    #
+    # Optional, defaults to `sub`.
+    #
+    #subject_claim: &quot;sub&quot;
+
+    # The issuer to validate the &quot;iss&quot; claim against.
+    #
+    # Optional, if provided the &quot;iss&quot; claim will be required and
+    # validated for all JSON web tokens.
+    #
+    #issuer: &quot;provided-by-your-issuer&quot;
+
+    # A list of audiences to validate the &quot;aud&quot; claim against.
+    #
+    # Optional, if provided the &quot;aud&quot; claim will be required and
+    # validated for all JSON web tokens.
+    #
+    # Note that if the &quot;aud&quot; claim is included in a JSON web token then
+    # validation will fail without configuring audiences.
+    #
+    #audiences:
+    #    - &quot;provided-by-your-issuer&quot;
+
+
+password_config:
+   # Uncomment to disable password login
+   #
+   #enabled: false
+
+   # Uncomment to disable authentication against the local password
+   # database. This is ignored if `enabled` is false, and is only useful
+   # if you have other password_providers.
+   #
+   #localdb_enabled: false
+
+   # Uncomment and change to a secret random string for extra security.
+   # DO NOT CHANGE THIS AFTER INITIAL SETUP!
+   #
+   #pepper: &quot;EVEN_MORE_SECRET&quot;
+
+   # Define and enforce a password policy. Each parameter is optional.
+   # This is an implementation of MSC2000.
+   #
+   policy:
+      # Whether to enforce the password policy.
+      # Defaults to 'false'.
+      #
+      #enabled: true
+
+      # Minimum accepted length for a password.
+      # Defaults to 0.
+      #
+      #minimum_length: 15
+
+      # Whether a password must contain at least one digit.
+      # Defaults to 'false'.
+      #
+      #require_digit: true
+
+      # Whether a password must contain at least one symbol.
+      # A symbol is any character that's not a number or a letter.
+      # Defaults to 'false'.
+      #
+      #require_symbol: true
+
+      # Whether a password must contain at least one lowercase letter.
+      # Defaults to 'false'.
+      #
+      #require_lowercase: true
+
+      # Whether a password must contain at least one uppercase letter.
+      # Defaults to 'false'.
+      #
+      #require_uppercase: true
+
+ui_auth:
+    # The amount of time to allow a user-interactive authentication session
+    # to be active.
+    #
+    # This defaults to 0, meaning the user is queried for their credentials
+    # before every action, but this can be overridden to allow a single
+    # validation to be re-used.  This weakens the protections afforded by
+    # the user-interactive authentication process, by allowing for multiple
+    # (and potentially different) operations to use the same validation session.
+    #
+    # This is ignored for potentially &quot;dangerous&quot; operations (including
+    # deactivating an account, modifying an account password, and
+    # adding a 3PID).
+    #
+    # Uncomment below to allow for credential validation to last for 15
+    # seconds.
+    #
+    #session_timeout: &quot;15s&quot;
+
+
+# Configuration for sending emails from Synapse.
+#
+# Server admins can configure custom templates for email content. See
+# https://matrix-org.github.io/synapse/latest/templates.html for more information.
+#
+email:
+  # The hostname of the outgoing SMTP server to use. Defaults to 'localhost'.
+  #
+  #smtp_host: mail.server
+
+  # The port on the mail server for outgoing SMTP. Defaults to 25.
+  #
+  #smtp_port: 587
+
+  # Username/password for authentication to the SMTP server. By default, no
+  # authentication is attempted.
+  #
+  #smtp_user: &quot;exampleusername&quot;
+  #smtp_pass: &quot;examplepassword&quot;
+
+  # Uncomment the following to require TLS transport security for SMTP.
+  # By default, Synapse will connect over plain text, and will then switch to
+  # TLS via STARTTLS *if the SMTP server supports it*. If this option is set,
+  # Synapse will refuse to connect unless the server supports STARTTLS.
+  #
+  #require_transport_security: true
+
+  # Uncomment the following to disable TLS for SMTP.
+  #
+  # By default, if the server supports TLS, it will be used, and the server
+  # must present a certificate that is valid for 'smtp_host'. If this option
+  # is set to false, TLS will not be used.
+  #
+  #enable_tls: false
+
+  # notif_from defines the &quot;From&quot; address to use when sending emails.
+  # It must be set if email sending is enabled.
+  #
+  # The placeholder '%(app)s' will be replaced by the application name,
+  # which is normally 'app_name' (below), but may be overridden by the
+  # Matrix client application.
+  #
+  # Note that the placeholder must be written '%(app)s', including the
+  # trailing 's'.
+  #
+  #notif_from: &quot;Your Friendly %(app)s homeserver &lt;noreply@example.com&gt;&quot;
+
+  # app_name defines the default value for '%(app)s' in notif_from and email
+  # subjects. It defaults to 'Matrix'.
+  #
+  #app_name: my_branded_matrix_server
+
+  # Uncomment the following to enable sending emails for messages that the user
+  # has missed. Disabled by default.
+  #
+  #enable_notifs: true
+
+  # Uncomment the following to disable automatic subscription to email
+  # notifications for new users. Enabled by default.
+  #
+  #notif_for_new_users: false
+
+  # Custom URL for client links within the email notifications. By default
+  # links will be based on &quot;https://matrix.to&quot;.
+  #
+  # (This setting used to be called riot_base_url; the old name is still
+  # supported for backwards-compatibility but is now deprecated.)
+  #
+  #client_base_url: &quot;http://localhost/riot&quot;
+
+  # Configure the time that a validation email will expire after sending.
+  # Defaults to 1h.
+  #
+  #validation_token_lifetime: 15m
+
+  # The web client location to direct users to during an invite. This is passed
+  # to the identity server as the org.matrix.web_client_location key. Defaults
+  # to unset, giving no guidance to the identity server.
+  #
+  #invite_client_location: https://app.element.io
+
+  # Subjects to use when sending emails from Synapse.
+  #
+  # The placeholder '%(app)s' will be replaced with the value of the 'app_name'
+  # setting above, or by a value dictated by the Matrix client application.
+  #
+  # If a subject isn't overridden in this configuration file, the value used as
+  # its example will be used.
+  #
+  #subjects:
+
+    # Subjects for notification emails.
+    #
+    # On top of the '%(app)s' placeholder, these can use the following
+    # placeholders:
+    #
+    #   * '%(person)s', which will be replaced by the display name of the user(s)
+    #      that sent the message(s), e.g. &quot;Alice and Bob&quot;.
+    #   * '%(room)s', which will be replaced by the name of the room the
+    #      message(s) have been sent to, e.g. &quot;My super room&quot;.
+    #
+    # See the example provided for each setting to see which placeholder can be
+    # used and how to use them.
+    #
+    # Subject to use to notify about one message from one or more user(s) in a
+    # room which has a name.
+    #message_from_person_in_room: &quot;[%(app)s] You have a message on %(app)s from %(person)s in the %(room)s room...&quot;
+    #
+    # Subject to use to notify about one message from one or more user(s) in a
+    # room which doesn't have a name.
+    #message_from_person: &quot;[%(app)s] You have a message on %(app)s from %(person)s...&quot;
+    #
+    # Subject to use to notify about multiple messages from one or more users in
+    # a room which doesn't have a name.
+    #messages_from_person: &quot;[%(app)s] You have messages on %(app)s from %(person)s...&quot;
+    #
+    # Subject to use to notify about multiple messages in a room which has a
+    # name.
+    #messages_in_room: &quot;[%(app)s] You have messages on %(app)s in the %(room)s room...&quot;
+    #
+    # Subject to use to notify about multiple messages in multiple rooms.
+    #messages_in_room_and_others: &quot;[%(app)s] You have messages on %(app)s in the %(room)s room and others...&quot;
+    #
+    # Subject to use to notify about multiple messages from multiple persons in
+    # multiple rooms. This is similar to the setting above except it's used when
+    # the room in which the notification was triggered has no name.
+    #messages_from_person_and_others: &quot;[%(app)s] You have messages on %(app)s from %(person)s and others...&quot;
+    #
+    # Subject to use to notify about an invite to a room which has a name.
+    #invite_from_person_to_room: &quot;[%(app)s] %(person)s has invited you to join the %(room)s room on %(app)s...&quot;
+    #
+    # Subject to use to notify about an invite to a room which doesn't have a
+    # name.
+    #invite_from_person: &quot;[%(app)s] %(person)s has invited you to chat on %(app)s...&quot;
+
+    # Subject for emails related to account administration.
+    #
+    # On top of the '%(app)s' placeholder, these one can use the
+    # '%(server_name)s' placeholder, which will be replaced by the value of the
+    # 'server_name' setting in your Synapse configuration.
+    #
+    # Subject to use when sending a password reset email.
+    #password_reset: &quot;[%(server_name)s] Password reset&quot;
+    #
+    # Subject to use when sending a verification email to assert an address's
+    # ownership.
+    #email_validation: &quot;[%(server_name)s] Validate your email&quot;
+
+
+
+## Push ##
+
+push:
+  # Clients requesting push notifications can either have the body of
+  # the message sent in the notification poke along with other details
+  # like the sender, or just the event ID and room ID (`event_id_only`).
+  # If clients choose the former, this option controls whether the
+  # notification request includes the content of the event (other details
+  # like the sender are still included). For `event_id_only` push, it
+  # has no effect.
+  #
+  # For modern android devices the notification content will still appear
+  # because it is loaded by the app. iPhone, however will send a
+  # notification saying only that a message arrived and who it came from.
+  #
+  # The default value is &quot;true&quot; to include message details. Uncomment to only
+  # include the event ID and room ID in push notification payloads.
+  #
+  #include_content: false
+
+  # When a push notification is received, an unread count is also sent.
+  # This number can either be calculated as the number of unread messages
+  # for the user, or the number of *rooms* the user has unread messages in.
+  #
+  # The default value is &quot;true&quot;, meaning push clients will see the number of
+  # rooms with unread messages in them. Uncomment to instead send the number
+  # of unread messages.
+  #
+  #group_unread_count_by_room: false
+
+
+## Rooms ##
+
+# Controls whether locally-created rooms should be end-to-end encrypted by
+# default.
+#
+# Possible options are &quot;all&quot;, &quot;invite&quot;, and &quot;off&quot;. They are defined as:
+#
+# * &quot;all&quot;: any locally-created room
+# * &quot;invite&quot;: any room created with the &quot;private_chat&quot; or &quot;trusted_private_chat&quot;
+#             room creation presets
+# * &quot;off&quot;: this option will take no effect
+#
+# The default value is &quot;off&quot;.
+#
+# Note that this option will only affect rooms created after it is set. It
+# will also not affect rooms created by other servers.
+#
+#encryption_enabled_by_default_for_room_type: invite
+
+
+# Uncomment to allow non-server-admin users to create groups on this server
+#
+#enable_group_creation: true
+
+# If enabled, non server admins can only create groups with local parts
+# starting with this prefix
+#
+#group_creation_prefix: &quot;unofficial_&quot;
+
+
+
+# User Directory configuration
+#
+user_directory:
+    # Defines whether users can search the user directory. If false then
+    # empty responses are returned to all queries. Defaults to true.
+    #
+    # Uncomment to disable the user directory.
+    #
+    #enabled: false
+
+    # Defines whether to search all users visible to your HS when searching
+    # the user directory. If false, search results will only contain users
+    # visible in public rooms and users sharing a room with the requester.
+    # Defaults to false.
+    #
+    # NB. If you set this to true, and the last time the user_directory search
+    # indexes were (re)built was before Synapse 1.44, you'll have to
+    # rebuild the indexes in order to search through all known users.
+    # These indexes are built the first time Synapse starts; admins can
+    # manually trigger a rebuild via API following the instructions at
+    #     https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/background_updates.html#run
+    #
+    # Uncomment to return search results containing all known users, even if that
+    # user does not share a room with the requester.
+    #
+    #search_all_users: true
+
+    # Defines whether to prefer local users in search query results.
+    # If True, local users are more likely to appear above remote users
+    # when searching the user directory. Defaults to false.
+    #
+    # Uncomment to prefer local over remote users in user directory search
+    # results.
+    #
+    #prefer_local_users: true
+
+
+# User Consent configuration
+#
+# for detailed instructions, see
+# https://matrix-org.github.io/synapse/latest/consent_tracking.html
+#
+# Parts of this section are required if enabling the 'consent' resource under
+# 'listeners', in particular 'template_dir' and 'version'.
+#
+# 'template_dir' gives the location of the templates for the HTML forms.
+# This directory should contain one subdirectory per language (eg, 'en', 'fr'),
+# and each language directory should contain the policy document (named as
+# '&lt;version&gt;.html') and a success page (success.html).
+#
+# 'version' specifies the 'current' version of the policy document. It defines
+# the version to be served by the consent resource if there is no 'v'
+# parameter.
+#
+# 'server_notice_content', if enabled, will send a user a &quot;Server Notice&quot;
+# asking them to consent to the privacy policy. The 'server_notices' section
+# must also be configured for this to work. Notices will *not* be sent to
+# guest users unless 'send_server_notice_to_guests' is set to true.
+#
+# 'block_events_error', if set, will block any attempts to send events
+# until the user consents to the privacy policy. The value of the setting is
+# used as the text of the error.
+#
+# 'require_at_registration', if enabled, will add a step to the registration
+# process, similar to how captcha works. Users will be required to accept the
+# policy before their account is created.
+#
+# 'policy_name' is the display name of the policy users will see when registering
+# for an account. Has no effect unless `require_at_registration` is enabled.
+# Defaults to &quot;Privacy Policy&quot;.
+#
+#user_consent:
+#  template_dir: res/templates/privacy
+#  version: 1.0
+#  server_notice_content:
+#    msgtype: m.text
+#    body: &gt;-
+#      To continue using this homeserver you must review and agree to the
+#      terms and conditions at %(consent_uri)s
+#  send_server_notice_to_guests: true
+#  block_events_error: &gt;-
+#    To continue using this homeserver you must review and agree to the
+#    terms and conditions at %(consent_uri)s
+#  require_at_registration: false
+#  policy_name: Privacy Policy
+#
+
+
+
+# Settings for local room and user statistics collection. See
+# https://matrix-org.github.io/synapse/latest/room_and_user_statistics.html.
+#
+stats:
+  # Uncomment the following to disable room and user statistics. Note that doing
+  # so may cause certain features (such as the room directory) not to work
+  # correctly.
+  #
+  #enabled: false
+
+
+# Server Notices room configuration
+#
+# Uncomment this section to enable a room which can be used to send notices
+# from the server to users. It is a special room which cannot be left; notices
+# come from a special &quot;notices&quot; user id.
+#
+# If you uncomment this section, you *must* define the system_mxid_localpart
+# setting, which defines the id of the user which will be used to send the
+# notices.
+#
+# It's also possible to override the room name, the display name of the
+# &quot;notices&quot; user, and the avatar for the user.
+#
+#server_notices:
+#  system_mxid_localpart: notices
+#  system_mxid_display_name: &quot;Server Notices&quot;
+#  system_mxid_avatar_url: &quot;mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ&quot;
+#  room_name: &quot;Server Notices&quot;
+
+
+
+# Uncomment to disable searching the public room list. When disabled
+# blocks searching local and remote room lists for local and remote
+# users by always returning an empty list for all queries.
+#
+#enable_room_list_search: false
+
+# The `alias_creation` option controls who's allowed to create aliases
+# on this server.
+#
+# The format of this option is a list of rules that contain globs that
+# match against user_id, room_id and the new alias (fully qualified with
+# server name). The action in the first rule that matches is taken,
+# which can currently either be &quot;allow&quot; or &quot;deny&quot;.
+#
+# Missing user_id/room_id/alias fields default to &quot;*&quot;.
+#
+# If no rules match the request is denied. An empty list means no one
+# can create aliases.
+#
+# Options for the rules include:
+#
+#   user_id: Matches against the creator of the alias
+#   alias: Matches against the alias being created
+#   room_id: Matches against the room ID the alias is being pointed at
+#   action: Whether to &quot;allow&quot; or &quot;deny&quot; the request if the rule matches
+#
+# The default is:
+#
+#alias_creation_rules:
+#  - user_id: &quot;*&quot;
+#    alias: &quot;*&quot;
+#    room_id: &quot;*&quot;
+#    action: allow
+
+# The `room_list_publication_rules` option controls who can publish and
+# which rooms can be published in the public room list.
+#
+# The format of this option is the same as that for
+# `alias_creation_rules`.
+#
+# If the room has one or more aliases associated with it, only one of
+# the aliases needs to match the alias rule. If there are no aliases
+# then only rules with `alias: *` match.
+#
+# If no rules match the request is denied. An empty list means no one
+# can publish rooms.
+#
+# Options for the rules include:
+#
+#   user_id: Matches against the creator of the alias
+#   room_id: Matches against the room ID being published
+#   alias: Matches against any current local or canonical aliases
+#            associated with the room
+#   action: Whether to &quot;allow&quot; or &quot;deny&quot; the request if the rule matches
+#
+# The default is:
+#
+#room_list_publication_rules:
+#  - user_id: &quot;*&quot;
+#    alias: &quot;*&quot;
+#    room_id: &quot;*&quot;
+#    action: allow
+
+
+## Opentracing ##
+
+# These settings enable opentracing, which implements distributed tracing.
+# This allows you to observe the causal chains of events across servers
+# including requests, key lookups etc., across any server running
+# synapse or any other other services which supports opentracing
+# (specifically those implemented with Jaeger).
+#
+opentracing:
+    # tracing is disabled by default. Uncomment the following line to enable it.
+    #
+    #enabled: true
+
+    # The list of homeservers we wish to send and receive span contexts and span baggage.
+    # See https://matrix-org.github.io/synapse/latest/opentracing.html.
+    #
+    # This is a list of regexes which are matched against the server_name of the
+    # homeserver.
+    #
+    # By default, it is empty, so no servers are matched.
+    #
+    #homeserver_whitelist:
+    #  - &quot;.*&quot;
+
+    # A list of the matrix IDs of users whose requests will always be traced,
+    # even if the tracing system would otherwise drop the traces due to
+    # probabilistic sampling.
+    #
+    # By default, the list is empty.
+    #
+    #force_tracing_for_users:
+    #  - &quot;@user1:server_name&quot;
+    #  - &quot;@user2:server_name&quot;
+
+    # Jaeger can be configured to sample traces at different rates.
+    # All configuration options provided by Jaeger can be set here.
+    # Jaeger's configuration is mostly related to trace sampling which
+    # is documented here:
+    # https://www.jaegertracing.io/docs/latest/sampling/.
+    #
+    #jaeger_config:
+    #  sampler:
+    #    type: const
+    #    param: 1
+    #  logging:
+    #    false
+
+
+## Workers ##
+
+# Disables sending of outbound federation transactions on the main process.
+# Uncomment if using a federation sender worker.
+#
+#send_federation: false
+
+# It is possible to run multiple federation sender workers, in which case the
+# work is balanced across them.
+#
+# This configuration must be shared between all federation sender workers, and if
+# changed all federation sender workers must be stopped at the same time and then
+# started, to ensure that all instances are running with the same config (otherwise
+# events may be dropped).
+#
+#federation_sender_instances:
+#  - federation_sender1
+
+# When using workers this should be a map from `worker_name` to the
+# HTTP replication listener of the worker, if configured.
+#
+#instance_map:
+#  worker1:
+#    host: localhost
+#    port: 8034
+
+# Experimental: When using workers you can define which workers should
+# handle event persistence and typing notifications. Any worker
+# specified here must also be in the `instance_map`.
+#
+#stream_writers:
+#  events: worker1
+#  typing: worker1
+
+# The worker that is used to run background tasks (e.g. cleaning up expired
+# data). If not provided this defaults to the main process.
+#
+#run_background_tasks_on: worker1
+
+# A shared secret used by the replication APIs to authenticate HTTP requests
+# from workers.
+#
+# By default this is unused and traffic is not authenticated.
+#
+#worker_replication_secret: &quot;&quot;
+
+
+# Configuration for Redis when using workers. This *must* be enabled when
+# using workers (unless using old style direct TCP configuration).
+#
+redis:
+  # Uncomment the below to enable Redis support.
+  #
+  #enabled: true
+
+  # Optional host and port to use to connect to redis. Defaults to
+  # localhost and 6379
+  #
+  #host: localhost
+  #port: 6379
+
+  # Optional password if configured on the Redis instance
+  #
+  #password: &lt;secret_password&gt;
+
+
+## Background Updates ##
+
+# Background updates are database updates that are run in the background in batches.
+# The duration, minimum batch size, default batch size, whether to sleep between batches and if so, how long to
+# sleep can all be configured. This is helpful to speed up or slow down the updates.
+#
+background_updates:
+    # How long in milliseconds to run a batch of background updates for. Defaults to 100. Uncomment and set
+    # a time to change the default.
+    #
+    #background_update_duration_ms: 500
+
+    # Whether to sleep between updates. Defaults to True. Uncomment to change the default.
+    #
+    #sleep_enabled: false
+
+    # If sleeping between updates, how long in milliseconds to sleep for. Defaults to 1000. Uncomment
+    # and set a duration to change the default.
+    #
+    #sleep_duration_ms: 300
+
+    # Minimum size a batch of background updates can be. Must be greater than 0. Defaults to 1. Uncomment and
+    # set a size to change the default.
+    #
+    #min_batch_size: 10
+
+    # The batch size to use for the first iteration of a new background update. The default is 100.
+    # Uncomment and set a size to change the default.
+    #
+    #default_batch_size: 50
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="logging-sample-configuration-file"><a class="header" href="#logging-sample-configuration-file">Logging Sample Configuration File</a></h1>
+<p>Below is a sample logging configuration file. This file can be tweaked to control how your
+homeserver will output logs. A restart of the server is generally required to apply any
+changes made to this file. The value of the <code>log_config</code> option in your homeserver
+config should be the path to this file.</p>
+<p>Note that a default logging configuration (shown below) is created automatically alongside
+the homeserver config when following the <a href="usage/configuration/../../setup/installation.html">installation instructions</a>.
+It should be named <code>&lt;SERVERNAME&gt;.log.config</code> by default.</p>
+<pre><code class="language-yaml"># Log configuration for Synapse.
+#
+# This is a YAML file containing a standard Python logging configuration
+# dictionary. See [1] for details on the valid settings.
+#
+# Synapse also supports structured logging for machine readable logs which can
+# be ingested by ELK stacks. See [2] for details.
+#
+# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
+# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
+
+version: 1
+
+formatters:
+    precise:
+        format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
+
+handlers:
+    file:
+        class: logging.handlers.TimedRotatingFileHandler
+        formatter: precise
+        filename: /var/log/matrix-synapse/homeserver.log
+        when: midnight
+        backupCount: 3  # Does not include the current log file.
+        encoding: utf8
+
+    # Default to buffering writes to log file for efficiency.
+    # WARNING/ERROR logs will still be flushed immediately, but there will be a
+    # delay (of up to `period` seconds, or until the buffer is full with
+    # `capacity` messages) before INFO/DEBUG logs get written.
+    buffer:
+        class: synapse.logging.handlers.PeriodicallyFlushingMemoryHandler
+        target: file
+
+        # The capacity is the maximum number of log lines that are buffered
+        # before being written to disk. Increasing this will lead to better
+        # performance, at the expensive of it taking longer for log lines to
+        # be written to disk.
+        # This parameter is required.
+        capacity: 10
+
+        # Logs with a level at or above the flush level will cause the buffer to
+        # be flushed immediately.
+        # Default value: 40 (ERROR)
+        # Other values: 50 (CRITICAL), 30 (WARNING), 20 (INFO), 10 (DEBUG)
+        flushLevel: 30  # Flush immediately for WARNING logs and higher
+
+        # The period of time, in seconds, between forced flushes.
+        # Messages will not be delayed for longer than this time.
+        # Default value: 5 seconds
+        period: 5
+
+    # A handler that writes logs to stderr. Unused by default, but can be used
+    # instead of &quot;buffer&quot; and &quot;file&quot; in the logger handlers.
+    console:
+        class: logging.StreamHandler
+        formatter: precise
+
+loggers:
+    synapse.storage.SQL:
+        # beware: increasing this to DEBUG will make synapse log sensitive
+        # information such as access tokens.
+        level: INFO
+
+    twisted:
+        # We send the twisted logging directly to the file handler,
+        # to work around https://github.com/matrix-org/synapse/issues/3471
+        # when using &quot;buffer&quot; logger. Use &quot;console&quot; to log to stderr instead.
+        handlers: [file]
+        propagate: false
+
+root:
+    level: INFO
+
+    # Write logs to the `buffer` handler, which will buffer them together in memory,
+    # then write them to a file.
+    #
+    # Replace &quot;buffer&quot; with &quot;console&quot; to log to stderr instead. (Note that you'll
+    # also need to update the configuration for the `twisted` logger above, in
+    # this case.)
+    #
+    handlers: [buffer]
+
+disable_existing_loggers: false
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="structured-logging"><a class="header" href="#structured-logging">Structured Logging</a></h1>
+<p>A structured logging system can be useful when your logs are destined for a
+machine to parse and process. By maintaining its machine-readable characteristics,
+it enables more efficient searching and aggregations when consumed by software
+such as the &quot;ELK stack&quot;.</p>
+<p>Synapse's structured logging system is configured via the file that Synapse's
+<code>log_config</code> config option points to. The file should include a formatter which
+uses the <code>synapse.logging.TerseJsonFormatter</code> class included with Synapse and a
+handler which uses the above formatter.</p>
+<p>There is also a <code>synapse.logging.JsonFormatter</code> option which does not include
+a timestamp in the resulting JSON. This is useful if the log ingester adds its
+own timestamp.</p>
+<p>A structured logging configuration looks similar to the following:</p>
+<pre><code class="language-yaml">version: 1
+
+formatters:
+    structured:
+        class: synapse.logging.TerseJsonFormatter
+
+handlers:
+    file:
+        class: logging.handlers.TimedRotatingFileHandler
+        formatter: structured
+        filename: /path/to/my/logs/homeserver.log
+        when: midnight
+        backupCount: 3  # Does not include the current log file.
+        encoding: utf8
+
+loggers:
+    synapse:
+        level: INFO
+        handlers: [remote]
+    synapse.storage.SQL:
+        level: WARNING
+</code></pre>
+<p>The above logging config will set Synapse as 'INFO' logging level by default,
+with the SQL layer at 'WARNING', and will log to a file, stored as JSON.</p>
+<p>It is also possible to figure Synapse to log to a remote endpoint by using the
+<code>synapse.logging.RemoteHandler</code> class included with Synapse. It takes the
+following arguments:</p>
+<ul>
+<li><code>host</code>: Hostname or IP address of the log aggregator.</li>
+<li><code>port</code>: Numerical port to contact on the host.</li>
+<li><code>maximum_buffer</code>: (Optional, defaults to 1000) The maximum buffer size to allow.</li>
+</ul>
+<p>A remote structured logging configuration looks similar to the following:</p>
+<pre><code class="language-yaml">version: 1
+
+formatters:
+    structured:
+        class: synapse.logging.TerseJsonFormatter
+
+handlers:
+    remote:
+        class: synapse.logging.RemoteHandler
+        formatter: structured
+        host: 10.1.2.3
+        port: 9999
+
+loggers:
+    synapse:
+        level: INFO
+        handlers: [remote]
+    synapse.storage.SQL:
+        level: WARNING
+</code></pre>
+<p>The above logging config will set Synapse as 'INFO' logging level by default,
+with the SQL layer at 'WARNING', and will log JSON formatted messages to a
+remote endpoint at 10.1.2.3:9999.</p>
+<h2 id="upgrading-from-legacy-structured-logging-configuration"><a class="header" href="#upgrading-from-legacy-structured-logging-configuration">Upgrading from legacy structured logging configuration</a></h2>
+<p>Versions of Synapse prior to v1.54.0 automatically converted the legacy
+structured logging configuration, which was deprecated in v1.23.0, to the standard
+library logging configuration.</p>
+<p>The following reference can be used to update your configuration. Based on the
+drain <code>type</code>, we can pick a new handler:</p>
+<ol>
+<li>For a type of <code>console</code>, <code>console_json</code>, or <code>console_json_terse</code>: a handler
+with a class of <code>logging.StreamHandler</code> and a <code>stream</code> of <code>ext://sys.stdout</code>
+or <code>ext://sys.stderr</code> should be used.</li>
+<li>For a type of <code>file</code> or <code>file_json</code>: a handler of <code>logging.FileHandler</code> with
+a location of the file path should be used.</li>
+<li>For a type of <code>network_json_terse</code>: a handler of <code>synapse.logging.RemoteHandler</code>
+with the host and port should be used.</li>
+</ol>
+<p>Then based on the drain <code>type</code> we can pick a new formatter:</p>
+<ol>
+<li>For a type of <code>console</code> or <code>file</code> no formatter is necessary.</li>
+<li>For a type of <code>console_json</code> or <code>file_json</code>: a formatter of
+<code>synapse.logging.JsonFormatter</code> should be used.</li>
+<li>For a type of <code>console_json_terse</code> or <code>network_json_terse</code>: a formatter of
+<code>synapse.logging.TerseJsonFormatter</code> should be used.</li>
+</ol>
+<p>For each new handler and formatter they should be added to the logging configuration
+and then assigned to either a logger or the root logger.</p>
+<p>An example legacy configuration:</p>
+<pre><code class="language-yaml">structured: true
+
+loggers:
+    synapse:
+        level: INFO
+    synapse.storage.SQL:
+        level: WARNING
+
+drains:
+    console:
+        type: console
+        location: stdout
+    file:
+        type: file_json
+        location: homeserver.log
+</code></pre>
+<p>Would be converted into a new configuration:</p>
+<pre><code class="language-yaml">version: 1
+
+formatters:
+    json:
+        class: synapse.logging.JsonFormatter
+
+handlers:
+    console:
+        class: logging.StreamHandler
+        stream: ext://sys.stdout
+    file:
+        class: logging.FileHandler
+        formatter: json
+        filename: homeserver.log
+
+loggers:
+    synapse:
+        level: INFO
+        handlers: [console, file]
+    synapse.storage.SQL:
+        level: WARNING
+</code></pre>
+<p>The new logging configuration is a bit more verbose, but significantly more
+flexible. It allows for configuration that were not previously possible, such as
+sending plain logs over the network, or using different handlers for different
+modules.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="templates"><a class="header" href="#templates">Templates</a></h1>
+<p>Synapse uses parametrised templates to generate the content of emails it sends and
+webpages it shows to users.</p>
+<p>By default, Synapse will use the templates listed <a href="https://github.com/matrix-org/synapse/tree/master/synapse/res/templates">here</a>.
+Server admins can configure an additional directory for Synapse to look for templates
+in, allowing them to specify custom templates:</p>
+<pre><code class="language-yaml">templates:
+  custom_templates_directory: /path/to/custom/templates/
+</code></pre>
+<p>If this setting is not set, or the files named below are not found within the directory,
+default templates from within the Synapse package will be used.</p>
+<p>Templates that are given variables when being rendered are rendered using <a href="https://jinja.palletsprojects.com/en/2.11.x/">Jinja 2</a>.
+Templates rendered by Jinja 2 can also access two functions on top of the functions
+already available as part of Jinja 2:</p>
+<pre><code class="language-python">format_ts(value: int, format: str) -&gt; str
+</code></pre>
+<p>Formats a timestamp in milliseconds.</p>
+<p>Example: <code>reason.last_sent_ts|format_ts(&quot;%c&quot;)</code></p>
+<pre><code class="language-python">mxc_to_http(value: str, width: int, height: int, resize_method: str = &quot;crop&quot;) -&gt; str
+</code></pre>
+<p>Turns a <code>mxc://</code> URL for media content into an HTTP(S) one using the homeserver's
+<code>public_baseurl</code> configuration setting as the URL's base.</p>
+<p>Example: <code>message.sender_avatar_url|mxc_to_http(32,32)</code></p>
+<pre><code class="language-python">localpart_from_email(address: str) -&gt; str
+</code></pre>
+<p>Returns the local part of an email address (e.g. <code>alice</code> in <code>alice@example.com</code>).</p>
+<p>Example: <code>user.email_address|localpart_from_email</code></p>
+<h2 id="email-templates"><a class="header" href="#email-templates">Email templates</a></h2>
+<p>Below are the templates Synapse will look for when generating the content of an email:</p>
+<ul>
+<li><code>notif_mail.html</code> and <code>notif_mail.txt</code>: The contents of email notifications of missed
+events.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>user_display_name</code>: the display name for the user receiving the notification</li>
+<li><code>unsubscribe_link</code>: the link users can click to unsubscribe from email notifications</li>
+<li><code>summary_text</code>: a summary of the notification(s). The text used can be customised
+by configuring the various settings in the <code>email.subjects</code> section of the
+configuration file.</li>
+<li><code>rooms</code>: a list of rooms containing events to include in the email. Each element is
+an object with the following attributes:
+<ul>
+<li><code>title</code>: a human-readable name for the room</li>
+<li><code>hash</code>: a hash of the ID of the room</li>
+<li><code>invite</code>: a boolean, which is <code>True</code> if the room is an invite the user hasn't
+accepted yet, <code>False</code> otherwise</li>
+<li><code>notifs</code>: a list of events, or an empty list if <code>invite</code> is <code>True</code>. Each element
+is an object with the following attributes:
+<ul>
+<li><code>link</code>: a <code>matrix.to</code> link to the event</li>
+<li><code>ts</code>: the time in milliseconds at which the event was received</li>
+<li><code>messages</code>: a list of messages containing one message before the event, the
+message in the event, and one message after the event. Each element is an
+object with the following attributes:
+<ul>
+<li><code>event_type</code>: the type of the event</li>
+<li><code>is_historical</code>: a boolean, which is <code>False</code> if the message is the one
+that triggered the notification, <code>True</code> otherwise</li>
+<li><code>id</code>: the ID of the event</li>
+<li><code>ts</code>: the time in milliseconds at which the event was sent</li>
+<li><code>sender_name</code>: the display name for the event's sender</li>
+<li><code>sender_avatar_url</code>: the avatar URL (as a <code>mxc://</code> URL) for the event's
+sender</li>
+<li><code>sender_hash</code>: a hash of the user ID of the sender</li>
+<li><code>msgtype</code>: the type of the message</li>
+<li><code>body_text_html</code>: html representation of the message</li>
+<li><code>body_text_plain</code>: plaintext representation of the message</li>
+<li><code>image_url</code>: mxc url of an image, when &quot;msgtype&quot; is &quot;m.image&quot;</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>link</code>: a <code>matrix.to</code> link to the room</li>
+<li><code>avator_url</code>: url to the room's avator</li>
+</ul>
+</li>
+<li><code>reason</code>: information on the event that triggered the email to be sent. It's an
+object with the following attributes:
+<ul>
+<li><code>room_id</code>: the ID of the room the event was sent in</li>
+<li><code>room_name</code>: a human-readable name for the room the event was sent in</li>
+<li><code>now</code>: the current time in milliseconds</li>
+<li><code>received_at</code>: the time in milliseconds at which the event was received</li>
+<li><code>delay_before_mail_ms</code>: the amount of time in milliseconds Synapse always waits
+before ever emailing about a notification (to give the user a chance to respond
+to other push or notice the window)</li>
+<li><code>last_sent_ts</code>: the time in milliseconds at which a notification was last sent
+for an event in this room</li>
+<li><code>throttle_ms</code>: the minimum amount of time in milliseconds between two
+notifications can be sent for this room</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>password_reset.html</code> and <code>password_reset.txt</code>: The contents of password reset emails
+sent by the homeserver.
+When rendering, these templates are given a <code>link</code> variable which contains the link the
+user must click in order to reset their password.</li>
+<li><code>registration.html</code> and <code>registration.txt</code>: The contents of address verification emails
+sent during registration.
+When rendering, these templates are given a <code>link</code> variable which contains the link the
+user must click in order to validate their email address.</li>
+<li><code>add_threepid.html</code> and <code>add_threepid.txt</code>: The contents of address verification emails
+sent when an address is added to a Matrix account.
+When rendering, these templates are given a <code>link</code> variable which contains the link the
+user must click in order to validate their email address.</li>
+</ul>
+<h2 id="html-page-templates-for-registration-and-password-reset"><a class="header" href="#html-page-templates-for-registration-and-password-reset">HTML page templates for registration and password reset</a></h2>
+<p>Below are the templates Synapse will look for when generating pages related to
+registration and password reset:</p>
+<ul>
+<li><code>password_reset_confirmation.html</code>: An HTML page that a user will see when they follow
+the link in the password reset email. The user will be asked to confirm the action
+before their password is reset.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>sid</code>: the session ID for the password reset</li>
+<li><code>token</code>: the token for the password reset</li>
+<li><code>client_secret</code>: the client secret for the password reset</li>
+</ul>
+</li>
+<li><code>password_reset_success.html</code> and <code>password_reset_failure.html</code>: HTML pages for success
+and failure that a user will see when they confirm the password reset flow using the
+page above.
+When rendering, <code>password_reset_success.html</code> is given no variable, and
+<code>password_reset_failure.html</code> is given a <code>failure_reason</code>, which contains the reason
+for the password reset failure. </li>
+<li><code>registration_success.html</code> and <code>registration_failure.html</code>: HTML pages for success and
+failure that a user will see when they follow the link in an address verification email
+sent during registration.
+When rendering, <code>registration_success.html</code> is given no variable, and
+<code>registration_failure.html</code> is given a <code>failure_reason</code>, which contains the reason
+for the registration failure.</li>
+<li><code>add_threepid_success.html</code> and <code>add_threepid_failure.html</code>: HTML pages for success and
+failure that a user will see when they follow the link in an address verification email
+sent when an address is added to a Matrix account.
+When rendering, <code>add_threepid_success.html</code> is given no variable, and
+<code>add_threepid_failure.html</code> is given a <code>failure_reason</code>, which contains the reason
+for the registration failure.</li>
+</ul>
+<h2 id="html-page-templates-for-single-sign-on-sso"><a class="header" href="#html-page-templates-for-single-sign-on-sso">HTML page templates for Single Sign-On (SSO)</a></h2>
+<p>Below are the templates Synapse will look for when generating pages related to SSO:</p>
+<ul>
+<li><code>sso_login_idp_picker.html</code>: HTML page to prompt the user to choose an
+Identity Provider during login.
+This is only used if multiple SSO Identity Providers are configured.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>redirect_url</code>: the URL that the user will be redirected to after
+login.</li>
+<li><code>server_name</code>: the homeserver's name.</li>
+<li><code>providers</code>: a list of available Identity Providers. Each element is
+an object with the following attributes:
+<ul>
+<li><code>idp_id</code>: unique identifier for the IdP</li>
+<li><code>idp_name</code>: user-facing name for the IdP</li>
+<li><code>idp_icon</code>: if specified in the IdP config, an MXC URI for an icon
+for the IdP</li>
+<li><code>idp_brand</code>: if specified in the IdP config, a textual identifier
+for the brand of the IdP
+The rendered HTML page should contain a form which submits its results
+back as a GET request, with the following query parameters:</li>
+</ul>
+</li>
+<li><code>redirectUrl</code>: the client redirect URI (ie, the <code>redirect_url</code> passed
+to the template)</li>
+<li><code>idp</code>: the 'idp_id' of the chosen IDP.</li>
+</ul>
+</li>
+<li><code>sso_auth_account_details.html</code>: HTML page to prompt new users to enter a
+userid and confirm other details. This is only shown if the
+SSO implementation (with any <code>user_mapping_provider</code>) does not return
+a localpart.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>server_name</code>: the homeserver's name.</li>
+<li><code>idp</code>: details of the SSO Identity Provider that the user logged in
+with: an object with the following attributes:
+<ul>
+<li><code>idp_id</code>: unique identifier for the IdP</li>
+<li><code>idp_name</code>: user-facing name for the IdP</li>
+<li><code>idp_icon</code>: if specified in the IdP config, an MXC URI for an icon
+for the IdP</li>
+<li><code>idp_brand</code>: if specified in the IdP config, a textual identifier
+for the brand of the IdP</li>
+</ul>
+</li>
+<li><code>user_attributes</code>: an object containing details about the user that
+we received from the IdP. May have the following attributes:
+<ul>
+<li><code>display_name</code>: the user's display name</li>
+<li><code>emails</code>: a list of email addresses</li>
+<li><code>localpart</code>: the local part of the Matrix user ID to register,
+if <code>localpart_template</code> is set in the mapping provider configuration (empty
+string if not)
+The template should render a form which submits the following fields:</li>
+</ul>
+</li>
+<li><code>username</code>: the localpart of the user's chosen user id</li>
+</ul>
+</li>
+<li><code>sso_new_user_consent.html</code>: HTML page allowing the user to consent to the
+server's terms and conditions. This is only shown for new users, and only if
+<code>user_consent.require_at_registration</code> is set.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>server_name</code>: the homeserver's name.</li>
+<li><code>user_id</code>: the user's matrix proposed ID.</li>
+<li><code>user_profile.display_name</code>: the user's proposed display name, if any.</li>
+<li>consent_version: the version of the terms that the user will be
+shown</li>
+<li><code>terms_url</code>: a link to the page showing the terms.
+The template should render a form which submits the following fields:</li>
+<li><code>accepted_version</code>: the version of the terms accepted by the user
+(ie, 'consent_version' from the input variables).</li>
+</ul>
+</li>
+<li><code>sso_redirect_confirm.html</code>: HTML page for a confirmation step before redirecting back
+to the client with the login token.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>redirect_url</code>: the URL the user is about to be redirected to.</li>
+<li><code>display_url</code>: the same as <code>redirect_url</code>, but with the query
+parameters stripped. The intention is to have a
+human-readable URL to show to users, not to use it as
+the final address to redirect to.</li>
+<li><code>server_name</code>: the homeserver's name.</li>
+<li><code>new_user</code>: a boolean indicating whether this is the user's first time
+logging in.</li>
+<li><code>user_id</code>: the user's matrix ID.</li>
+<li><code>user_profile.avatar_url</code>: an MXC URI for the user's avatar, if any.
+<code>None</code> if the user has not set an avatar.</li>
+<li><code>user_profile.display_name</code>: the user's display name. <code>None</code> if the user
+has not set a display name.</li>
+</ul>
+</li>
+<li><code>sso_auth_confirm.html</code>: HTML page which notifies the user that they are authenticating
+to confirm an operation on their account during the user interactive authentication
+process.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>redirect_url</code>: the URL the user is about to be redirected to.</li>
+<li><code>description</code>: the operation which the user is being asked to confirm</li>
+<li><code>idp</code>: details of the Identity Provider that we will use to confirm
+the user's identity: an object with the following attributes:
+<ul>
+<li><code>idp_id</code>: unique identifier for the IdP</li>
+<li><code>idp_name</code>: user-facing name for the IdP</li>
+<li><code>idp_icon</code>: if specified in the IdP config, an MXC URI for an icon
+for the IdP</li>
+<li><code>idp_brand</code>: if specified in the IdP config, a textual identifier
+for the brand of the IdP</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>sso_auth_success.html</code>: HTML page shown after a successful user interactive
+authentication session.
+Note that this page must include the JavaScript which notifies of a successful
+authentication (see https://matrix.org/docs/spec/client_server/r0.6.0#fallback).
+This template has no additional variables.</li>
+<li><code>sso_auth_bad_user.html</code>: HTML page shown after a user-interactive authentication
+session which does not map correctly onto the expected user.
+When rendering, this template is given the following variables:
+<ul>
+<li><code>server_name</code>: the homeserver's name.</li>
+<li><code>user_id_to_verify</code>: the MXID of the user that we are trying to
+validate.</li>
+</ul>
+</li>
+<li><code>sso_account_deactivated.html</code>: HTML page shown during single sign-on if a deactivated
+user (according to Synapse's database) attempts to login.
+This template has no additional variables.</li>
+<li><code>sso_error.html</code>: HTML page to display to users if something goes wrong during the
+OpenID Connect authentication process.
+When rendering, this template is given two variables:
+<ul>
+<li><code>error</code>: the technical name of the error</li>
+<li><code>error_description</code>: a human-readable message for the error</li>
+</ul>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="user-authentication"><a class="header" href="#user-authentication">User Authentication</a></h1>
+<p>Synapse supports multiple methods of authenticating users, either out-of-the-box or through custom pluggable
+authentication modules.</p>
+<p>Included in Synapse is support for authenticating users via:</p>
+<ul>
+<li>A username and password.</li>
+<li>An email address and password.</li>
+<li>Single Sign-On through the SAML, Open ID Connect or CAS protocols.</li>
+<li>JSON Web Tokens.</li>
+<li>An administrator's shared secret.</li>
+</ul>
+<p>Synapse can additionally be extended to support custom authentication schemes through optional &quot;password auth provider&quot;
+modules.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="single-sign-on"><a class="header" href="#single-sign-on">Single Sign-On</a></h1>
+<p>Synapse supports single sign-on through the SAML, Open ID Connect or CAS protocols. 
+LDAP and other login methods are supported through first and third-party password
+auth provider modules.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="configuring-synapse-to-authenticate-against-an-openid-connect-provider"><a class="header" href="#configuring-synapse-to-authenticate-against-an-openid-connect-provider">Configuring Synapse to authenticate against an OpenID Connect provider</a></h1>
+<p>Synapse can be configured to use an OpenID Connect Provider (OP) for
+authentication, instead of its own local password database.</p>
+<p>Any OP should work with Synapse, as long as it supports the authorization code
+flow. There are a few options for that:</p>
+<ul>
+<li>
+<p>start a local OP. Synapse has been tested with <a href="https://www.ory.sh/docs/hydra/">Hydra</a> and
+<a href="https://github.com/dexidp/dex">Dex</a>.  Note that for an OP to work, it should be served under a
+secure (HTTPS) origin.  A certificate signed with a self-signed, locally
+trusted CA should work. In that case, start Synapse with a <code>SSL_CERT_FILE</code>
+environment variable set to the path of the CA.</p>
+</li>
+<li>
+<p>set up a SaaS OP, like <a href="https://developers.google.com/identity/protocols/oauth2/openid-connect">Google</a>, <a href="https://auth0.com/">Auth0</a> or
+<a href="https://www.okta.com/">Okta</a>. Synapse has been tested with Auth0 and Google.</p>
+</li>
+</ul>
+<p>It may also be possible to use other OAuth2 providers which provide the
+<a href="https://tools.ietf.org/html/rfc6749#section-4.1">authorization code grant type</a>,
+such as <a href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps">Github</a>.</p>
+<h2 id="preparing-synapse"><a class="header" href="#preparing-synapse">Preparing Synapse</a></h2>
+<p>The OpenID integration in Synapse uses the
+<a href="https://pypi.org/project/Authlib/"><code>authlib</code></a> library, which must be installed
+as follows:</p>
+<ul>
+<li>
+<p>The relevant libraries are included in the Docker images and Debian packages
+provided by <code>matrix.org</code> so no further action is needed.</p>
+</li>
+<li>
+<p>If you installed Synapse into a virtualenv, run <code>/path/to/env/bin/pip install matrix-synapse[oidc]</code> to install the necessary dependencies.</p>
+</li>
+<li>
+<p>For other installation mechanisms, see the documentation provided by the
+maintainer.</p>
+</li>
+</ul>
+<p>To enable the OpenID integration, you should then add a section to the <code>oidc_providers</code>
+setting in your configuration file (or uncomment one of the existing examples).
+See <a href="./sample_config.yaml">sample_config.yaml</a> for some sample settings, as well as
+the text below for example configurations for specific providers.</p>
+<h2 id="sample-configs"><a class="header" href="#sample-configs">Sample configs</a></h2>
+<p>Here are a few configs for providers that should work with Synapse.</p>
+<h3 id="microsoft-azure-active-directory"><a class="header" href="#microsoft-azure-active-directory">Microsoft Azure Active Directory</a></h3>
+<p>Azure AD can act as an OpenID Connect Provider. Register a new application under
+<em>App registrations</em> in the Azure AD management console. The RedirectURI for your
+application should point to your matrix server:
+<code>[synapse public baseurl]/_synapse/client/oidc/callback</code></p>
+<p>Go to <em>Certificates &amp; secrets</em> and register a new client secret. Make note of your
+Directory (tenant) ID as it will be used in the Azure links.
+Edit your Synapse config file and change the <code>oidc_config</code> section:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: microsoft
+    idp_name: Microsoft
+    issuer: &quot;https://login.microsoftonline.com/&lt;tenant id&gt;/v2.0&quot;
+    client_id: &quot;&lt;client id&gt;&quot;
+    client_secret: &quot;&lt;client secret&gt;&quot;
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    authorization_endpoint: &quot;https://login.microsoftonline.com/&lt;tenant id&gt;/oauth2/v2.0/authorize&quot;
+    token_endpoint: &quot;https://login.microsoftonline.com/&lt;tenant id&gt;/oauth2/v2.0/token&quot;
+    userinfo_endpoint: &quot;https://graph.microsoft.com/oidc/userinfo&quot;
+
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username.split('@')[0] }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="dex"><a class="header" href="#dex">Dex</a></h3>
+<p><a href="https://github.com/dexidp/dex">Dex</a> is a simple, open-source OpenID Connect Provider.
+Although it is designed to help building a full-blown provider with an
+external database, it can be configured with static passwords in a config file.</p>
+<p>Follow the <a href="https://dexidp.io/docs/getting-started/">Getting Started guide</a>
+to install Dex.</p>
+<p>Edit <code>examples/config-dev.yaml</code> config file from the Dex repo to add a client:</p>
+<pre><code class="language-yaml">staticClients:
+- id: synapse
+  secret: secret
+  redirectURIs:
+  - '[synapse public baseurl]/_synapse/client/oidc/callback'
+  name: 'Synapse'
+</code></pre>
+<p>Run with <code>dex serve examples/config-dev.yaml</code>.</p>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: dex
+    idp_name: &quot;My Dex server&quot;
+    skip_verification: true # This is needed as Dex is served on an insecure endpoint
+    issuer: &quot;http://127.0.0.1:5556/dex&quot;
+    client_id: &quot;synapse&quot;
+    client_secret: &quot;secret&quot;
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.name }}&quot;
+        display_name_template: &quot;{{ user.name|capitalize }}&quot;
+</code></pre>
+<h3 id="keycloak"><a class="header" href="#keycloak">Keycloak</a></h3>
+<p><a href="https://www.keycloak.org/docs/latest/server_admin/#sso-protocols">Keycloak</a> is an opensource IdP maintained by Red Hat.</p>
+<p>Follow the <a href="https://www.keycloak.org/getting-started">Getting Started Guide</a> to install Keycloak and set up a realm.</p>
+<ol>
+<li>
+<p>Click <code>Clients</code> in the sidebar and click <code>Create</code></p>
+</li>
+<li>
+<p>Fill in the fields as below:</p>
+</li>
+</ol>
+<table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>
+<tr><td>Client ID</td><td><code>synapse</code></td></tr>
+<tr><td>Client Protocol</td><td><code>openid-connect</code></td></tr>
+</tbody></table>
+<ol start="3">
+<li>Click <code>Save</code></li>
+<li>Fill in the fields as below:</li>
+</ol>
+<table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>
+<tr><td>Client ID</td><td><code>synapse</code></td></tr>
+<tr><td>Enabled</td><td><code>On</code></td></tr>
+<tr><td>Client Protocol</td><td><code>openid-connect</code></td></tr>
+<tr><td>Access Type</td><td><code>confidential</code></td></tr>
+<tr><td>Valid Redirect URIs</td><td><code>[synapse public baseurl]/_synapse/client/oidc/callback</code></td></tr>
+</tbody></table>
+<ol start="5">
+<li>Click <code>Save</code></li>
+<li>On the Credentials tab, update the fields:</li>
+</ol>
+<table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>
+<tr><td>Client Authenticator</td><td><code>Client ID and Secret</code></td></tr>
+</tbody></table>
+<ol start="7">
+<li>Click <code>Regenerate Secret</code></li>
+<li>Copy Secret</li>
+</ol>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: keycloak
+    idp_name: &quot;My KeyCloak server&quot;
+    issuer: &quot;https://127.0.0.1:8443/auth/realms/{realm_name}&quot;
+    client_id: &quot;synapse&quot;
+    client_secret: &quot;copy secret generated from above&quot;
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="auth0"><a class="header" href="#auth0">Auth0</a></h3>
+<p><a href="https://auth0.com/">Auth0</a> is a hosted SaaS IdP solution.</p>
+<ol>
+<li>
+<p>Create a regular web application for Synapse</p>
+</li>
+<li>
+<p>Set the Allowed Callback URLs to <code>[synapse public baseurl]/_synapse/client/oidc/callback</code></p>
+</li>
+<li>
+<p>Add a rule to add the <code>preferred_username</code> claim.</p>
+<details>
+ <summary>Code sample</summary>
+<pre><code class="language-js">function addPersistenceAttribute(user, context, callback) {
+  user.user_metadata = user.user_metadata || {};
+  user.user_metadata.preferred_username = user.user_metadata.preferred_username || user.user_id;
+  context.idToken.preferred_username = user.user_metadata.preferred_username;
+
+  auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
+    .then(function(){
+        callback(null, user, context);
+    })
+    .catch(function(err){
+        callback(err);
+    });
+}
+</code></pre>
+</li>
+</ol>
+</details>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: auth0
+    idp_name: Auth0
+    issuer: &quot;https://your-tier.eu.auth0.com/&quot; # TO BE FILLED
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="authentik"><a class="header" href="#authentik">Authentik</a></h3>
+<p><a href="https://goauthentik.io/">Authentik</a> is an open-source IdP solution.</p>
+<ol>
+<li>Create a provider in Authentik, with type OAuth2/OpenID.</li>
+<li>The parameters are:</li>
+</ol>
+<ul>
+<li>Client Type: Confidential</li>
+<li>JWT Algorithm: RS256</li>
+<li>Scopes: OpenID, Email and Profile</li>
+<li>RSA Key: Select any available key</li>
+<li>Redirect URIs: <code>[synapse public baseurl]/_synapse/client/oidc/callback</code></li>
+</ul>
+<ol start="3">
+<li>Create an application for synapse in Authentik and link it to the provider.</li>
+<li>Note the slug of your application, Client ID and Client Secret.</li>
+</ol>
+<p>Note: RSA keys must be used for signing for Authentik, ECC keys do not work.</p>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: authentik
+    idp_name: authentik
+    discover: true
+    issuer: &quot;https://your.authentik.example.org/application/o/your-app-slug/&quot; # TO BE FILLED: domain and slug
+    client_id: &quot;your client id&quot; # TO BE FILLED
+    client_secret: &quot;your client secret&quot; # TO BE FILLED
+    scopes:
+      - &quot;openid&quot;
+      - &quot;profile&quot;
+      - &quot;email&quot;
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}&quot;
+        display_name_template: &quot;{{ user.preferred_username|capitalize }}&quot; # TO BE FILLED: If your users have names in Authentik and you want those in Synapse, this should be replaced with user.name|capitalize.
+</code></pre>
+<h3 id="lemonldap"><a class="header" href="#lemonldap">LemonLDAP</a></h3>
+<p><a href="https://lemonldap-ng.org/">LemonLDAP::NG</a> is an open-source IdP solution.</p>
+<ol>
+<li>Create an OpenID Connect Relying Parties in LemonLDAP::NG</li>
+<li>The parameters are:</li>
+</ol>
+<ul>
+<li>Client ID under the basic menu of the new Relying Parties (<code>Options &gt; Basic &gt; Client ID</code>)</li>
+<li>Client secret (<code>Options &gt; Basic &gt; Client secret</code>)</li>
+<li>JWT Algorithm: RS256 within the security menu of the new Relying Parties
+(<code>Options &gt; Security &gt; ID Token signature algorithm</code> and <code>Options &gt; Security &gt; Access Token signature algorithm</code>)</li>
+<li>Scopes: OpenID, Email and Profile</li>
+<li>Allowed redirection addresses for login (<code>Options &gt; Basic &gt; Allowed redirection addresses for login</code> ) :
+<code>[synapse public baseurl]/_synapse/client/oidc/callback</code></li>
+</ul>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: lemonldap
+    idp_name: lemonldap
+    discover: true
+    issuer: &quot;https://auth.example.org/&quot; # TO BE FILLED: replace with your domain
+    client_id: &quot;your client id&quot; # TO BE FILLED
+    client_secret: &quot;your client secret&quot; # TO BE FILLED
+    scopes:
+      - &quot;openid&quot;
+      - &quot;profile&quot;
+      - &quot;email&quot;
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}}&quot;
+        # TO BE FILLED: If your users have names in LemonLDAP::NG and you want those in Synapse, this should be replaced with user.name|capitalize or any valid filter.
+        display_name_template: &quot;{{ user.preferred_username|capitalize }}&quot;
+</code></pre>
+<h3 id="github"><a class="header" href="#github">GitHub</a></h3>
+<p><a href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps">GitHub</a> is a bit special as it is not an OpenID Connect compliant provider, but
+just a regular OAuth2 provider.</p>
+<p>The <a href="https://developer.github.com/v3/users/#get-the-authenticated-user"><code>/user</code> API endpoint</a>
+can be used to retrieve information on the authenticated user. As the Synapse
+login mechanism needs an attribute to uniquely identify users, and that endpoint
+does not return a <code>sub</code> property, an alternative <code>subject_claim</code> has to be set.</p>
+<ol>
+<li>Create a new OAuth application: https://github.com/settings/applications/new.</li>
+<li>Set the callback URL to <code>[synapse public baseurl]/_synapse/client/oidc/callback</code>.</li>
+</ol>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: github
+    idp_name: Github
+    idp_brand: &quot;github&quot;  # optional: styling hint for clients
+    discover: false
+    issuer: &quot;https://github.com/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    authorization_endpoint: &quot;https://github.com/login/oauth/authorize&quot;
+    token_endpoint: &quot;https://github.com/login/oauth/access_token&quot;
+    userinfo_endpoint: &quot;https://api.github.com/user&quot;
+    scopes: [&quot;read:user&quot;]
+    user_mapping_provider:
+      config:
+        subject_claim: &quot;id&quot;
+        localpart_template: &quot;{{ user.login }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="google"><a class="header" href="#google">Google</a></h3>
+<p><a href="https://developers.google.com/identity/protocols/oauth2/openid-connect">Google</a> is an OpenID certified authentication and authorisation provider.</p>
+<ol>
+<li>Set up a project in the Google API Console (see
+https://developers.google.com/identity/protocols/oauth2/openid-connect#appsetup).</li>
+<li>Add an &quot;OAuth Client ID&quot; for a Web Application under &quot;Credentials&quot;.</li>
+<li>Copy the Client ID and Client Secret, and add the following to your synapse config:
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: google
+    idp_name: Google
+    idp_brand: &quot;google&quot;  # optional: styling hint for clients
+    issuer: &quot;https://accounts.google.com/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.given_name|lower }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+</li>
+<li>Back in the Google console, add this Authorized redirect URI: <code>[synapse public baseurl]/_synapse/client/oidc/callback</code>.</li>
+</ol>
+<h3 id="twitch"><a class="header" href="#twitch">Twitch</a></h3>
+<ol>
+<li>Setup a developer account on <a href="https://dev.twitch.tv/">Twitch</a></li>
+<li>Obtain the OAuth 2.0 credentials by <a href="https://dev.twitch.tv/console/apps/">creating an app</a></li>
+<li>Add this OAuth Redirect URL: <code>[synapse public baseurl]/_synapse/client/oidc/callback</code></li>
+</ol>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: twitch
+    idp_name: Twitch
+    issuer: &quot;https://id.twitch.tv/oauth2/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    client_auth_method: &quot;client_secret_post&quot;
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="gitlab"><a class="header" href="#gitlab">GitLab</a></h3>
+<ol>
+<li>Create a <a href="https://gitlab.com/profile/applications">new application</a>.</li>
+<li>Add the <code>read_user</code> and <code>openid</code> scopes.</li>
+<li>Add this Callback URL: <code>[synapse public baseurl]/_synapse/client/oidc/callback</code></li>
+</ol>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: gitlab
+    idp_name: Gitlab
+    idp_brand: &quot;gitlab&quot;  # optional: styling hint for clients
+    issuer: &quot;https://gitlab.com/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    client_auth_method: &quot;client_secret_post&quot;
+    scopes: [&quot;openid&quot;, &quot;read_user&quot;]
+    user_profile_method: &quot;userinfo_endpoint&quot;
+    user_mapping_provider:
+      config:
+        localpart_template: '{{ user.nickname }}'
+        display_name_template: '{{ user.name }}'
+</code></pre>
+<h3 id="facebook"><a class="header" href="#facebook">Facebook</a></h3>
+<ol start="0">
+<li>You will need a Facebook developer account. You can register for one
+<a href="https://developers.facebook.com/async/registration/">here</a>.</li>
+<li>On the <a href="https://developers.facebook.com/apps/">apps</a> page of the developer
+console, &quot;Create App&quot;, and choose &quot;Build Connected Experiences&quot;.</li>
+<li>Once the app is created, add &quot;Facebook Login&quot; and choose &quot;Web&quot;. You don't
+need to go through the whole form here.</li>
+<li>In the left-hand menu, open &quot;Products&quot;/&quot;Facebook Login&quot;/&quot;Settings&quot;.
+<ul>
+<li>Add <code>[synapse public baseurl]/_synapse/client/oidc/callback</code> as an OAuth Redirect
+URL.</li>
+</ul>
+</li>
+<li>In the left-hand menu, open &quot;Settings/Basic&quot;. Here you can copy the &quot;App ID&quot;
+and &quot;App Secret&quot; for use below.</li>
+</ol>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">  - idp_id: facebook
+    idp_name: Facebook
+    idp_brand: &quot;facebook&quot;  # optional: styling hint for clients
+    discover: false
+    issuer: &quot;https://www.facebook.com&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    scopes: [&quot;openid&quot;, &quot;email&quot;]
+    authorization_endpoint: &quot;https://facebook.com/dialog/oauth&quot;
+    token_endpoint: &quot;https://graph.facebook.com/v9.0/oauth/access_token&quot;
+    jwks_uri: &quot;https://www.facebook.com/.well-known/oauth/openid/jwks/&quot;
+    user_mapping_provider:
+      config:
+        display_name_template: &quot;{{ user.name }}&quot;
+        email_template: &quot;{{ '{{ user.email }}' }}&quot;
+</code></pre>
+<p>Relevant documents:</p>
+<ul>
+<li><a href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow">Manually Build a Login Flow</a></li>
+<li><a href="https://developers.facebook.com/docs/graph-api/using-graph-api/">Using Facebook's Graph API</a></li>
+<li><a href="https://developers.facebook.com/docs/graph-api/reference/user">Reference to the User endpoint</a></li>
+</ul>
+<p>Facebook do have an <a href="https://www.facebook.com/.well-known/openid-configuration">OIDC discovery endpoint</a>,
+but it has a <code>response_types_supported</code> which excludes &quot;code&quot; (which we rely on, and
+is even mentioned in their <a href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login">documentation</a>),
+so we have to disable discovery and configure the URIs manually.</p>
+<h3 id="gitea"><a class="header" href="#gitea">Gitea</a></h3>
+<p>Gitea is, like Github, not an OpenID provider, but just an OAuth2 provider.</p>
+<p>The <a href="https://try.gitea.io/api/swagger#/user/userGetCurrent"><code>/user</code> API endpoint</a>
+can be used to retrieve information on the authenticated user. As the Synapse
+login mechanism needs an attribute to uniquely identify users, and that endpoint
+does not return a <code>sub</code> property, an alternative <code>subject_claim</code> has to be set.</p>
+<ol>
+<li>Create a new application.</li>
+<li>Add this Callback URL: <code>[synapse public baseurl]/_synapse/client/oidc/callback</code></li>
+</ol>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: gitea
+    idp_name: Gitea
+    discover: false
+    issuer: &quot;https://your-gitea.com/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_secret: &quot;your-client-secret&quot; # TO BE FILLED
+    client_auth_method: client_secret_post
+    scopes: [] # Gitea doesn't support Scopes
+    authorization_endpoint: &quot;https://your-gitea.com/login/oauth/authorize&quot;
+    token_endpoint: &quot;https://your-gitea.com/login/oauth/access_token&quot;
+    userinfo_endpoint: &quot;https://your-gitea.com/api/v1/user&quot;
+    user_mapping_provider:
+      config:
+        subject_claim: &quot;id&quot;
+        localpart_template: &quot;{{ user.login }}&quot;
+        display_name_template: &quot;{{ user.full_name }}&quot;
+</code></pre>
+<h3 id="xwiki"><a class="header" href="#xwiki">XWiki</a></h3>
+<p>Install <a href="https://extensions.xwiki.org/xwiki/bin/view/Extension/OpenID%20Connect/OpenID%20Connect%20Provider/">OpenID Connect Provider</a> extension in your <a href="https://www.xwiki.org">XWiki</a> instance.</p>
+<p>Synapse config:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: xwiki
+    idp_name: &quot;XWiki&quot;
+    issuer: &quot;https://myxwikihost/xwiki/oidc/&quot;
+    client_id: &quot;your-client-id&quot; # TO BE FILLED
+    client_auth_method: none
+    scopes: [&quot;openid&quot;, &quot;profile&quot;]
+    user_profile_method: &quot;userinfo_endpoint&quot;
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.preferred_username }}&quot;
+        display_name_template: &quot;{{ user.name }}&quot;
+</code></pre>
+<h3 id="apple"><a class="header" href="#apple">Apple</a></h3>
+<p>Configuring &quot;Sign in with Apple&quot; (SiWA) requires an Apple Developer account.</p>
+<p>You will need to create a new &quot;Services ID&quot; for SiWA, and create and download a
+private key with &quot;SiWA&quot; enabled.</p>
+<p>As well as the private key file, you will need:</p>
+<ul>
+<li>Client ID: the &quot;identifier&quot; you gave the &quot;Services ID&quot;</li>
+<li>Team ID: a 10-character ID associated with your developer account.</li>
+<li>Key ID: the 10-character identifier for the key.</li>
+</ul>
+<p>https://help.apple.com/developer-account/?lang=en#/dev77c875b7e has more
+documentation on setting up SiWA.</p>
+<p>The synapse config will look like this:</p>
+<pre><code class="language-yaml">  - idp_id: apple
+    idp_name: Apple
+    issuer: &quot;https://appleid.apple.com&quot;
+    client_id: &quot;your-client-id&quot; # Set to the &quot;identifier&quot; for your &quot;ServicesID&quot;
+    client_auth_method: &quot;client_secret_post&quot;
+    client_secret_jwt_key:
+      key_file: &quot;/path/to/AuthKey_KEYIDCODE.p8&quot;  # point to your key file
+      jwt_header:
+        alg: ES256
+        kid: &quot;KEYIDCODE&quot;   # Set to the 10-char Key ID
+      jwt_payload:
+        iss: TEAMIDCODE    # Set to the 10-char Team ID
+    scopes: [&quot;name&quot;, &quot;email&quot;, &quot;openid&quot;]
+    authorization_endpoint: https://appleid.apple.com/auth/authorize?response_mode=form_post
+    user_mapping_provider:
+      config:
+        email_template: &quot;{{ user.email }}&quot;
+</code></pre>
+<h3 id="django-oauth-toolkit"><a class="header" href="#django-oauth-toolkit">Django OAuth Toolkit</a></h3>
+<p><a href="https://github.com/jazzband/django-oauth-toolkit">django-oauth-toolkit</a> is a
+Django application providing out of the box all the endpoints, data and logic
+needed to add OAuth2 capabilities to your Django projects. It supports
+<a href="https://django-oauth-toolkit.readthedocs.io/en/latest/oidc.html">OpenID Connect too</a>.</p>
+<p>Configuration on Django's side:</p>
+<ol>
+<li>Add an application: https://example.com/admin/oauth2_provider/application/add/ and choose parameters like this:</li>
+</ol>
+<ul>
+<li><code>Redirect uris</code>: https://synapse.example.com/_synapse/client/oidc/callback</li>
+<li><code>Client type</code>: <code>Confidential</code></li>
+<li><code>Authorization grant type</code>: <code>Authorization code</code></li>
+<li><code>Algorithm</code>: <code>HMAC with SHA-2 256</code></li>
+</ul>
+<ol start="2">
+<li>
+<p>You can <a href="https://django-oauth-toolkit.readthedocs.io/en/latest/oidc.html#customizing-the-oidc-responses">customize the claims</a> Django gives to synapse (optional):</p>
+<details>
+ <summary>Code sample</summary>
+<pre><code class="language-python">class CustomOAuth2Validator(OAuth2Validator):
+
+    def get_additional_claims(self, request):
+        return {
+            &quot;sub&quot;: request.user.email,
+            &quot;email&quot;: request.user.email,
+            &quot;first_name&quot;: request.user.first_name,
+            &quot;last_name&quot;: request.user.last_name,
+        }
+</code></pre>
+</details>
+</li>
+</ol>
+<p>Your synapse config is then:</p>
+<pre><code class="language-yaml">oidc_providers:
+  - idp_id: django_example
+    idp_name: &quot;Django Example&quot;
+    issuer: &quot;https://example.com/o/&quot;
+    client_id: &quot;your-client-id&quot;  # CHANGE ME
+    client_secret: &quot;your-client-secret&quot;  # CHANGE ME
+    scopes: [&quot;openid&quot;]
+    user_profile_method: &quot;userinfo_endpoint&quot;  # needed because oauth-toolkit does not include user information in the authorization response
+    user_mapping_provider:
+      config:
+        localpart_template: &quot;{{ user.email.split('@')[0] }}&quot;
+        display_name_template: &quot;{{ user.first_name }} {{ user.last_name }}&quot;
+        email_template: &quot;{{ user.email }}&quot;
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="saml"><a class="header" href="#saml">SAML</a></h1>
+<p>Synapse supports authenticating users via the <a href="https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language">Security Assertion
+Markup Language</a>
+(SAML) protocol natively.</p>
+<p>Please see the <code>saml2_config</code> and <code>sso</code> sections of the <a href="usage/configuration/user_authentication/single_sign_on/../../../configuration/homeserver_sample_config.html">Synapse configuration
+file</a> for more details.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="cas"><a class="header" href="#cas">CAS</a></h1>
+<p>Synapse supports authenticating users via the <a href="https://en.wikipedia.org/wiki/Central_Authentication_Service">Central Authentication
+Service protocol</a>
+(CAS) natively.</p>
+<p>Please see the <code>cas_config</code> and <code>sso</code> sections of the <a href="usage/configuration/user_authentication/single_sign_on/../../../configuration/homeserver_sample_config.html">Synapse configuration
+file</a> for more details.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="sso-mapping-providers"><a class="header" href="#sso-mapping-providers">SSO Mapping Providers</a></h1>
+<p>A mapping provider is a Python class (loaded via a Python module) that
+works out how to map attributes of a SSO response to Matrix-specific
+user attributes. Details such as user ID localpart, displayname, and even avatar
+URLs are all things that can be mapped from talking to a SSO service.</p>
+<p>As an example, a SSO service may return the email address
+&quot;john.smith@example.com&quot; for a user, whereas Synapse will need to figure out how
+to turn that into a displayname when creating a Matrix user for this individual.
+It may choose <code>John Smith</code>, or <code>Smith, John [Example.com]</code> or any number of
+variations. As each Synapse configuration may want something different, this is
+where SAML mapping providers come into play.</p>
+<p>SSO mapping providers are currently supported for OpenID and SAML SSO
+configurations. Please see the details below for how to implement your own.</p>
+<p>It is up to the mapping provider whether the user should be assigned a predefined
+Matrix ID based on the SSO attributes, or if the user should be allowed to
+choose their own username.</p>
+<p>In the first case - where users are automatically allocated a Matrix ID - it is
+the responsibility of the mapping provider to normalise the SSO attributes and
+map them to a valid Matrix ID. The <a href="https://matrix.org/docs/spec/appendices#user-identifiers">specification for Matrix
+IDs</a> has some
+information about what is considered valid.</p>
+<p>If the mapping provider does not assign a Matrix ID, then Synapse will
+automatically serve an HTML page allowing the user to pick their own username.</p>
+<p>External mapping providers are provided to Synapse in the form of an external
+Python module. You can retrieve this module from <a href="https://pypi.org">PyPI</a> or elsewhere,
+but it must be importable via Synapse (e.g. it must be in the same virtualenv
+as Synapse). The Synapse config is then modified to point to the mapping provider
+(and optionally provide additional configuration for it).</p>
+<h2 id="openid-mapping-providers"><a class="header" href="#openid-mapping-providers">OpenID Mapping Providers</a></h2>
+<p>The OpenID mapping provider can be customized by editing the
+<code>oidc_config.user_mapping_provider.module</code> config option.</p>
+<p><code>oidc_config.user_mapping_provider.config</code> allows you to provide custom
+configuration options to the module. Check with the module's documentation for
+what options it provides (if any). The options listed by default are for the
+user mapping provider built in to Synapse. If using a custom module, you should
+comment these options out and use those specified by the module instead.</p>
+<h3 id="building-a-custom-openid-mapping-provider"><a class="header" href="#building-a-custom-openid-mapping-provider">Building a Custom OpenID Mapping Provider</a></h3>
+<p>A custom mapping provider must specify the following methods:</p>
+<ul>
+<li><code>def __init__(self, parsed_config)</code>
+<ul>
+<li>Arguments:
+<ul>
+<li><code>parsed_config</code> - A configuration object that is the return value of the
+<code>parse_config</code> method. You should set any configuration options needed by
+the module here.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>def parse_config(config)</code>
+<ul>
+<li>This method should have the <code>@staticmethod</code> decoration.</li>
+<li>Arguments:
+<ul>
+<li><code>config</code> - A <code>dict</code> representing the parsed content of the
+<code>oidc_config.user_mapping_provider.config</code> homeserver config option.
+Runs on homeserver startup. Providers should extract and validate
+any option values they need here.</li>
+</ul>
+</li>
+<li>Whatever is returned will be passed back to the user mapping provider module's
+<code>__init__</code> method during construction.</li>
+</ul>
+</li>
+<li><code>def get_remote_user_id(self, userinfo)</code>
+<ul>
+<li>Arguments:
+<ul>
+<li><code>userinfo</code> - A <code>authlib.oidc.core.claims.UserInfo</code> object to extract user
+information from.</li>
+</ul>
+</li>
+<li>This method must return a string, which is the unique, immutable identifier
+for the user. Commonly the <code>sub</code> claim of the response.</li>
+</ul>
+</li>
+<li><code>async def map_user_attributes(self, userinfo, token, failures)</code>
+<ul>
+<li>This method must be async.</li>
+<li>Arguments:
+<ul>
+<li><code>userinfo</code> - A <code>authlib.oidc.core.claims.UserInfo</code> object to extract user
+information from.</li>
+<li><code>token</code> - A dictionary which includes information necessary to make
+further requests to the OpenID provider.</li>
+<li><code>failures</code> - An <code>int</code> that represents the amount of times the returned
+mxid localpart mapping has failed.  This should be used
+to create a deduplicated mxid localpart which should be
+returned instead. For example, if this method returns
+<code>john.doe</code> as the value of <code>localpart</code> in the returned
+dict, and that is already taken on the homeserver, this
+method will be called again with the same parameters but
+with failures=1. The method should then return a different
+<code>localpart</code> value, such as <code>john.doe1</code>.</li>
+</ul>
+</li>
+<li>Returns a dictionary with two keys:
+<ul>
+<li><code>localpart</code>: A string, used to generate the Matrix ID. If this is
+<code>None</code>, the user is prompted to pick their own username. This is only used
+during a user's first login. Once a localpart has been associated with a
+remote user ID (see <code>get_remote_user_id</code>) it cannot be updated.</li>
+<li><code>displayname</code>: An optional string, the display name for the user.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>async def get_extra_attributes(self, userinfo, token)</code>
+<ul>
+<li>
+<p>This method must be async.</p>
+</li>
+<li>
+<p>Arguments:</p>
+<ul>
+<li><code>userinfo</code> - A <code>authlib.oidc.core.claims.UserInfo</code> object to extract user
+information from.</li>
+<li><code>token</code> - A dictionary which includes information necessary to make
+further requests to the OpenID provider.</li>
+</ul>
+</li>
+<li>
+<p>Returns a dictionary that is suitable to be serialized to JSON. This
+will be returned as part of the response during a successful login.</p>
+<p>Note that care should be taken to not overwrite any of the parameters
+usually returned as part of the <a href="https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login">login response</a>.</p>
+</li>
+</ul>
+</li>
+</ul>
+<h3 id="default-openid-mapping-provider"><a class="header" href="#default-openid-mapping-provider">Default OpenID Mapping Provider</a></h3>
+<p>Synapse has a built-in OpenID mapping provider if a custom provider isn't
+specified in the config. It is located at
+<a href="https://github.com/matrix-org/synapse/blob/develop/synapse/handlers/oidc.py"><code>synapse.handlers.oidc.JinjaOidcMappingProvider</code></a>.</p>
+<h2 id="saml-mapping-providers"><a class="header" href="#saml-mapping-providers">SAML Mapping Providers</a></h2>
+<p>The SAML mapping provider can be customized by editing the
+<code>saml2_config.user_mapping_provider.module</code> config option.</p>
+<p><code>saml2_config.user_mapping_provider.config</code> allows you to provide custom
+configuration options to the module. Check with the module's documentation for
+what options it provides (if any). The options listed by default are for the
+user mapping provider built in to Synapse. If using a custom module, you should
+comment these options out and use those specified by the module instead.</p>
+<h3 id="building-a-custom-saml-mapping-provider"><a class="header" href="#building-a-custom-saml-mapping-provider">Building a Custom SAML Mapping Provider</a></h3>
+<p>A custom mapping provider must specify the following methods:</p>
+<ul>
+<li><code>def __init__(self, parsed_config, module_api)</code>
+<ul>
+<li>Arguments:
+<ul>
+<li><code>parsed_config</code> - A configuration object that is the return value of the
+<code>parse_config</code> method. You should set any configuration options needed by
+the module here.</li>
+<li><code>module_api</code> - a <code>synapse.module_api.ModuleApi</code> object which provides the
+stable API available for extension modules.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>def parse_config(config)</code>
+<ul>
+<li><strong>This method should have the <code>@staticmethod</code> decoration.</strong></li>
+<li>Arguments:
+<ul>
+<li><code>config</code> - A <code>dict</code> representing the parsed content of the
+<code>saml_config.user_mapping_provider.config</code> homeserver config option.
+Runs on homeserver startup. Providers should extract and validate
+any option values they need here.</li>
+</ul>
+</li>
+<li>Whatever is returned will be passed back to the user mapping provider module's
+<code>__init__</code> method during construction.</li>
+</ul>
+</li>
+<li><code>def get_saml_attributes(config)</code>
+<ul>
+<li><strong>This method should have the <code>@staticmethod</code> decoration.</strong></li>
+<li>Arguments:
+<ul>
+<li><code>config</code> - A object resulting from a call to <code>parse_config</code>.</li>
+</ul>
+</li>
+<li>Returns a tuple of two sets. The first set equates to the SAML auth
+response attributes that are required for the module to function, whereas
+the second set consists of those attributes which can be used if available,
+but are not necessary.</li>
+</ul>
+</li>
+<li><code>def get_remote_user_id(self, saml_response, client_redirect_url)</code>
+<ul>
+<li>Arguments:
+<ul>
+<li><code>saml_response</code> - A <code>saml2.response.AuthnResponse</code> object to extract user
+information from.</li>
+<li><code>client_redirect_url</code> - A string, the URL that the client will be
+redirected to.</li>
+</ul>
+</li>
+<li>This method must return a string, which is the unique, immutable identifier
+for the user. Commonly the <code>uid</code> claim of the response.</li>
+</ul>
+</li>
+<li><code>def saml_response_to_user_attributes(self, saml_response, failures, client_redirect_url)</code>
+<ul>
+<li>
+<p>Arguments:</p>
+<ul>
+<li><code>saml_response</code> - A <code>saml2.response.AuthnResponse</code> object to extract user
+information from.</li>
+<li><code>failures</code> - An <code>int</code> that represents the amount of times the returned
+mxid localpart mapping has failed.  This should be used
+to create a deduplicated mxid localpart which should be
+returned instead. For example, if this method returns
+<code>john.doe</code> as the value of <code>mxid_localpart</code> in the returned
+dict, and that is already taken on the homeserver, this
+method will be called again with the same parameters but
+with failures=1. The method should then return a different
+<code>mxid_localpart</code> value, such as <code>john.doe1</code>.</li>
+<li><code>client_redirect_url</code> - A string, the URL that the client will be
+redirected to.</li>
+</ul>
+</li>
+<li>
+<p>This method must return a dictionary, which will then be used by Synapse
+to build a new user. The following keys are allowed:</p>
+<ul>
+<li><code>mxid_localpart</code> - A string, the mxid localpart of the new user. If this is
+<code>None</code>, the user is prompted to pick their own username. This is only used
+during a user's first login. Once a localpart has been associated with a
+remote user ID (see <code>get_remote_user_id</code>) it cannot be updated.</li>
+<li><code>displayname</code> - The displayname of the new user. If not provided, will default to
+the value of <code>mxid_localpart</code>.</li>
+<li><code>emails</code> - A list of emails for the new user. If not provided, will
+default to an empty list.</li>
+</ul>
+<p>Alternatively it can raise a <code>synapse.api.errors.RedirectException</code> to
+redirect the user to another page. This is useful to prompt the user for
+additional information, e.g. if you want them to provide their own username.
+It is the responsibility of the mapping provider to either redirect back
+to <code>client_redirect_url</code> (including any additional information) or to
+complete registration using methods from the <code>ModuleApi</code>.</p>
+</li>
+</ul>
+</li>
+</ul>
+<h3 id="default-saml-mapping-provider"><a class="header" href="#default-saml-mapping-provider">Default SAML Mapping Provider</a></h3>
+<p>Synapse has a built-in SAML mapping provider if a custom provider isn't
+specified in the config. It is located at
+<a href="https://github.com/matrix-org/synapse/blob/develop/synapse/handlers/saml.py"><code>synapse.handlers.saml.DefaultSamlMappingProvider</code></a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 style="color:red">
+This page of the Synapse documentation is now deprecated. For up to date
+documentation on setting up or writing a password auth provider module, please see
+<a href="modules/index.html">this page</a>.
+</h2>
+<h1 id="password-auth-provider-modules"><a class="header" href="#password-auth-provider-modules">Password auth provider modules</a></h1>
+<p>Password auth providers offer a way for server administrators to
+integrate their Synapse installation with an existing authentication
+system.</p>
+<p>A password auth provider is a Python class which is dynamically loaded
+into Synapse, and provides a number of methods by which it can integrate
+with the authentication system.</p>
+<p>This document serves as a reference for those looking to implement their
+own password auth providers. Additionally, here is a list of known
+password auth provider module implementations:</p>
+<ul>
+<li><a href="https://github.com/matrix-org/matrix-synapse-ldap3/">matrix-synapse-ldap3</a></li>
+<li><a href="https://github.com/devture/matrix-synapse-shared-secret-auth">matrix-synapse-shared-secret-auth</a></li>
+<li><a href="https://github.com/ma1uta/matrix-synapse-rest-password-provider">matrix-synapse-rest-password-provider</a></li>
+</ul>
+<h2 id="required-methods"><a class="header" href="#required-methods">Required methods</a></h2>
+<p>Password auth provider classes must provide the following methods:</p>
+<ul>
+<li>
+<p><code>parse_config(config)</code>
+This method is passed the <code>config</code> object for this module from the
+homeserver configuration file.</p>
+<p>It should perform any appropriate sanity checks on the provided
+configuration, and return an object which is then passed into
+<code>__init__</code>.</p>
+<p>This method should have the <code>@staticmethod</code> decoration.</p>
+</li>
+<li>
+<p><code>__init__(self, config, account_handler)</code></p>
+<p>The constructor is passed the config object returned by
+<code>parse_config</code>, and a <code>synapse.module_api.ModuleApi</code> object which
+allows the password provider to check if accounts exist and/or create
+new ones.</p>
+</li>
+</ul>
+<h2 id="optional-methods"><a class="header" href="#optional-methods">Optional methods</a></h2>
+<p>Password auth provider classes may optionally provide the following methods:</p>
+<ul>
+<li>
+<p><code>get_db_schema_files(self)</code></p>
+<p>This method, if implemented, should return an Iterable of
+<code>(name, stream)</code> pairs of database schema files. Each file is applied
+in turn at initialisation, and a record is then made in the database
+so that it is not re-applied on the next start.</p>
+</li>
+<li>
+<p><code>get_supported_login_types(self)</code></p>
+<p>This method, if implemented, should return a <code>dict</code> mapping from a
+login type identifier (such as <code>m.login.password</code>) to an iterable
+giving the fields which must be provided by the user in the submission
+to <a href="https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login">the <code>/login</code> API</a>.
+These fields are passed in the <code>login_dict</code> dictionary to <code>check_auth</code>.</p>
+<p>For example, if a password auth provider wants to implement a custom
+login type of <code>com.example.custom_login</code>, where the client is expected
+to pass the fields <code>secret1</code> and <code>secret2</code>, the provider should
+implement this method and return the following dict:</p>
+<pre><code class="language-python">{&quot;com.example.custom_login&quot;: (&quot;secret1&quot;, &quot;secret2&quot;)}
+</code></pre>
+</li>
+<li>
+<p><code>check_auth(self, username, login_type, login_dict)</code></p>
+<p>This method does the real work. If implemented, it
+will be called for each login attempt where the login type matches one
+of the keys returned by <code>get_supported_login_types</code>.</p>
+<p>It is passed the (possibly unqualified) <code>user</code> field provided by the client,
+the login type, and a dictionary of login secrets passed by the
+client.</p>
+<p>The method should return an <code>Awaitable</code> object, which resolves
+to the canonical <code>@localpart:domain</code> user ID if authentication is
+successful, and <code>None</code> if not.</p>
+<p>Alternatively, the <code>Awaitable</code> can resolve to a <code>(str, func)</code> tuple, in
+which case the second field is a callback which will be called with
+the result from the <code>/login</code> call (including <code>access_token</code>,
+<code>device_id</code>, etc.)</p>
+</li>
+<li>
+<p><code>check_3pid_auth(self, medium, address, password)</code></p>
+<p>This method, if implemented, is called when a user attempts to
+register or log in with a third party identifier, such as email. It is
+passed the medium (ex. &quot;email&quot;), an address (ex.
+&quot;<a href="mailto:jdoe@example.com">jdoe@example.com</a>&quot;) and the user's password.</p>
+<p>The method should return an <code>Awaitable</code> object, which resolves
+to a <code>str</code> containing the user's (canonical) User id if
+authentication was successful, and <code>None</code> if not.</p>
+<p>As with <code>check_auth</code>, the <code>Awaitable</code> may alternatively resolve to a
+<code>(user_id, callback)</code> tuple.</p>
+</li>
+<li>
+<p><code>check_password(self, user_id, password)</code></p>
+<p>This method provides a simpler interface than
+<code>get_supported_login_types</code> and <code>check_auth</code> for password auth
+providers that just want to provide a mechanism for validating
+<code>m.login.password</code> logins.</p>
+<p>If implemented, it will be called to check logins with an
+<code>m.login.password</code> login type. It is passed a qualified
+<code>@localpart:domain</code> user id, and the password provided by the user.</p>
+<p>The method should return an <code>Awaitable</code> object, which resolves
+to <code>True</code> if authentication is successful, and <code>False</code> if not.</p>
+</li>
+<li>
+<p><code>on_logged_out(self, user_id, device_id, access_token)</code></p>
+<p>This method, if implemented, is called when a user logs out. It is
+passed the qualified user ID, the ID of the deactivated device (if
+any: access tokens are occasionally created without an associated
+device ID), and the (now deactivated) access token.</p>
+<p>It may return an <code>Awaitable</code> object; the logout request will
+wait for the <code>Awaitable</code> to complete, but the result is ignored.</p>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="jwt-login-type"><a class="header" href="#jwt-login-type">JWT Login Type</a></h1>
+<p>Synapse comes with a non-standard login type to support
+<a href="https://en.wikipedia.org/wiki/JSON_Web_Token">JSON Web Tokens</a>. In general the
+documentation for
+<a href="https://matrix.org/docs/spec/client_server/r0.6.1#login">the login endpoint</a>
+is still valid (and the mechanism works similarly to the
+<a href="https://matrix.org/docs/spec/client_server/r0.6.1#token-based">token based login</a>).</p>
+<p>To log in using a JSON Web Token, clients should submit a <code>/login</code> request as
+follows:</p>
+<pre><code class="language-json">{
+  &quot;type&quot;: &quot;org.matrix.login.jwt&quot;,
+  &quot;token&quot;: &quot;&lt;jwt&gt;&quot;
+}
+</code></pre>
+<p>Note that the login type of <code>m.login.jwt</code> is supported, but is deprecated. This
+will be removed in a future version of Synapse.</p>
+<p>The <code>token</code> field should include the JSON web token with the following claims:</p>
+<ul>
+<li>A claim that encodes the local part of the user ID is required. By default,
+the <code>sub</code> (subject) claim is used, or a custom claim can be set in the
+configuration file.</li>
+<li>The expiration time (<code>exp</code>), not before time (<code>nbf</code>), and issued at (<code>iat</code>)
+claims are optional, but validated if present.</li>
+<li>The issuer (<code>iss</code>) claim is optional, but required and validated if configured.</li>
+<li>The audience (<code>aud</code>) claim is optional, but required and validated if configured.
+Providing the audience claim when not configured will cause validation to fail.</li>
+</ul>
+<p>In the case that the token is not valid, the homeserver must respond with
+<code>403 Forbidden</code> and an error code of <code>M_FORBIDDEN</code>.</p>
+<p>As with other login types, there are additional fields (e.g. <code>device_id</code> and
+<code>initial_device_display_name</code>) which can be included in the above request.</p>
+<h2 id="preparing-synapse-1"><a class="header" href="#preparing-synapse-1">Preparing Synapse</a></h2>
+<p>The JSON Web Token integration in Synapse uses the
+<a href="https://pypi.org/project/pyjwt/"><code>PyJWT</code></a> library, which must be installed
+as follows:</p>
+<ul>
+<li>
+<p>The relevant libraries are included in the Docker images and Debian packages
+provided by <code>matrix.org</code> so no further action is needed.</p>
+</li>
+<li>
+<p>If you installed Synapse into a virtualenv, run <code>/path/to/env/bin/pip install synapse[pyjwt]</code> to install the necessary dependencies.</p>
+</li>
+<li>
+<p>For other installation mechanisms, see the documentation provided by the
+maintainer.</p>
+</li>
+</ul>
+<p>To enable the JSON web token integration, you should then add an <code>jwt_config</code> section
+to your configuration file (or uncomment the <code>enabled: true</code> line in the
+existing section). See <a href="./sample_config.yaml">sample_config.yaml</a> for some
+sample settings.</p>
+<h2 id="how-to-test-jwt-as-a-developer"><a class="header" href="#how-to-test-jwt-as-a-developer">How to test JWT as a developer</a></h2>
+<p>Although JSON Web Tokens are typically generated from an external server, the
+examples below use <a href="https://pyjwt.readthedocs.io/en/latest/">PyJWT</a> directly.</p>
+<ol>
+<li>
+<p>Configure Synapse with JWT logins, note that this example uses a pre-shared
+secret and an algorithm of HS256:</p>
+<pre><code class="language-yaml">jwt_config:
+    enabled: true
+    secret: &quot;my-secret-token&quot;
+    algorithm: &quot;HS256&quot;
+</code></pre>
+</li>
+<li>
+<p>Generate a JSON web token:</p>
+<pre><code class="language-bash">$ pyjwt --key=my-secret-token --alg=HS256 encode sub=test-user
+eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXVzZXIifQ.Ag71GT8v01UO3w80aqRPTeuVPBIBZkYhNTJJ-_-zQIc
+</code></pre>
+</li>
+<li>
+<p>Query for the login types and ensure <code>org.matrix.login.jwt</code> is there:</p>
+<pre><code class="language-bash">curl http://localhost:8080/_matrix/client/r0/login
+</code></pre>
+</li>
+<li>
+<p>Login used the generated JSON web token from above:</p>
+<pre><code class="language-bash">$ curl http://localhost:8082/_matrix/client/r0/login -X POST \
+    --data '{&quot;type&quot;:&quot;org.matrix.login.jwt&quot;,&quot;token&quot;:&quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXVzZXIifQ.Ag71GT8v01UO3w80aqRPTeuVPBIBZkYhNTJJ-_-zQIc&quot;}'
+{
+    &quot;access_token&quot;: &quot;&lt;access token&gt;&quot;,
+    &quot;device_id&quot;: &quot;ACBDEFGHI&quot;,
+    &quot;home_server&quot;: &quot;localhost:8080&quot;,
+    &quot;user_id&quot;: &quot;@test-user:localhost:8480&quot;
+}
+</code></pre>
+</li>
+</ol>
+<p>You should now be able to use the returned access token to query the client API.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="refresh-tokens"><a class="header" href="#refresh-tokens">Refresh Tokens</a></h1>
+<p>Synapse supports refresh tokens since version 1.49 (some earlier versions had support for an earlier, experimental draft of <a href="https://github.com/matrix-org/matrix-doc/blob/main/proposals/2918-refreshtokens.md#msc2918-refresh-tokens">MSC2918</a> which is not compatible).</p>
+<h2 id="background-and-motivation"><a class="header" href="#background-and-motivation">Background and motivation</a></h2>
+<p>Synapse users' sessions are identified by <strong>access tokens</strong>; access tokens are
+issued to users on login. Each session gets a unique access token which identifies
+it; the access token must be kept secret as it grants access to the user's account.</p>
+<p>Traditionally, these access tokens were eternally valid (at least until the user
+explicitly chose to log out).</p>
+<p>In some cases, it may be desirable for these access tokens to expire so that the
+potential damage caused by leaking an access token is reduced.
+On the other hand, forcing a user to re-authenticate (log in again) often might
+be too much of an inconvenience.</p>
+<p><strong>Refresh tokens</strong> are a mechanism to avoid some of this inconvenience whilst
+still getting most of the benefits of short access token lifetimes.
+Refresh tokens are also a concept present in OAuth 2 — further reading is available
+<a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.5">here</a>.</p>
+<p>When refresh tokens are in use, both an access token and a refresh token will be
+issued to users on login. The access token will expire after a predetermined amount
+of time, but otherwise works in the same way as before. When the access token is
+close to expiring (or has expired), the user's client should present the homeserver
+(Synapse) with the refresh token.</p>
+<p>The homeserver will then generate a new access token and refresh token for the user
+and return them. The old refresh token is invalidated and can not be used again*.</p>
+<p>Finally, refresh tokens also make it possible for sessions to be logged out if they
+are inactive for too long, before the session naturally ends; see the configuration
+guide below.</p>
+<p>*To prevent issues if clients lose connection half-way through refreshing a token,
+the refresh token is only invalidated once the new access token has been used at
+least once. For all intents and purposes, the above simplification is sufficient.</p>
+<h2 id="caveats"><a class="header" href="#caveats">Caveats</a></h2>
+<p>There are some caveats:</p>
+<ul>
+<li>If a third party gets both your access token and refresh token, they will be able to
+continue to enjoy access to your session.
+<ul>
+<li>This is still an improvement because you (the user) will notice when <em>your</em>
+session expires and you're not able to use your refresh token.
+That would be a giveaway that someone else has compromised your session.
+You would be able to log in again and terminate that session.
+Previously (with long-lived access tokens), a third party that has your access
+token could go undetected for a very long time.</li>
+</ul>
+</li>
+<li>Clients need to implement support for refresh tokens in order for them to be a
+useful mechanism.
+<ul>
+<li>It is up to homeserver administrators if they want to issue long-lived access
+tokens to clients not implementing refresh tokens.
+<ul>
+<li>For compatibility, it is likely that they should, at least until client support
+is widespread.
+<ul>
+<li>Users with clients that support refresh tokens will still benefit from the
+added security; it's not possible to downgrade a session to using long-lived
+access tokens so this effectively gives users the choice.</li>
+</ul>
+</li>
+<li>In a closed environment where all users use known clients, this may not be
+an issue as the homeserver administrator can know if the clients have refresh
+token support. In that case, the non-refreshable access token lifetime
+may be set to a short duration so that a similar level of security is provided.</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+<h2 id="configuration-guide"><a class="header" href="#configuration-guide">Configuration Guide</a></h2>
+<p>The following configuration options, in the <code>registration</code> section, are related:</p>
+<ul>
+<li><code>session_lifetime</code>: maximum length of a session, even if it's refreshed.
+In other words, the client must log in again after this time period.
+In most cases, this can be unset (infinite) or set to a long time (years or months).</li>
+<li><code>refreshable_access_token_lifetime</code>: lifetime of access tokens that are created
+by clients supporting refresh tokens.
+This should be short; a good value might be 5 minutes (<code>5m</code>).</li>
+<li><code>nonrefreshable_access_token_lifetime</code>: lifetime of access tokens that are created
+by clients which don't support refresh tokens.
+Make this short if you want to effectively force use of refresh tokens.
+Make this long if you don't want to inconvenience users of clients which don't
+support refresh tokens (by forcing them to frequently re-authenticate using
+login credentials).</li>
+<li><code>refresh_token_lifetime</code>: lifetime of refresh tokens.
+In other words, the client must refresh within this time period to maintain its session.
+Unless you want to log inactive sessions out, it is often fine to use a long
+value here or even leave it unset (infinite).
+Beware that making it too short will inconvenience clients that do not connect
+very often, including mobile clients and clients of infrequent users (by making
+it more difficult for them to refresh in time, which may force them to need to
+re-authenticate using login credentials).</li>
+</ul>
+<p><strong>Note:</strong> All four options above only apply when tokens are created (by logging in or refreshing).
+Changes to these settings do not apply retroactively.</p>
+<h3 id="using-refresh-token-expiry-to-log-out-inactive-sessions"><a class="header" href="#using-refresh-token-expiry-to-log-out-inactive-sessions">Using refresh token expiry to log out inactive sessions</a></h3>
+<p>If you'd like to force sessions to be logged out upon inactivity, you can enable
+refreshable access token expiry and refresh token expiry.</p>
+<p>This works because a client must refresh at least once within a period of
+<code>refresh_token_lifetime</code> in order to maintain valid credentials to access the
+account.</p>
+<p>(It's suggested that <code>refresh_token_lifetime</code> should be longer than
+<code>refreshable_access_token_lifetime</code> and this section assumes that to be the case
+for simplicity.)</p>
+<p>Note: this will only affect sessions using refresh tokens. You may wish to
+set a short <code>nonrefreshable_access_token_lifetime</code> to prevent this being bypassed
+by clients that do not support refresh tokens.</p>
+<h4 id="choosing-values-that-guarantee-permitting-some-inactivity"><a class="header" href="#choosing-values-that-guarantee-permitting-some-inactivity">Choosing values that guarantee permitting some inactivity</a></h4>
+<p>It may be desirable to permit some short periods of inactivity, for example to
+accommodate brief outages in client connectivity.</p>
+<p>The following model aims to provide guidance for choosing <code>refresh_token_lifetime</code>
+and <code>refreshable_access_token_lifetime</code> to satisfy requirements of the form:</p>
+<ol>
+<li>inactivity longer than <code>L</code> <strong>MUST</strong> cause the session to be logged out; and</li>
+<li>inactivity shorter than <code>S</code> <strong>MUST NOT</strong> cause the session to be logged out.</li>
+</ol>
+<p>This model makes the weakest assumption that all active clients will refresh as
+needed to maintain an active access token, but no sooner.
+<em>In reality, clients may refresh more often than this model assumes, but the
+above requirements will still hold.</em></p>
+<p>To satisfy the above model,</p>
+<ul>
+<li><code>refresh_token_lifetime</code> should be set to <code>L</code>; and</li>
+<li><code>refreshable_access_token_lifetime</code> should be set to <code>L - S</code>.</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="overview-2"><a class="header" href="#overview-2">Overview</a></h1>
+<p>A captcha can be enabled on your homeserver to help prevent bots from registering
+accounts. Synapse currently uses Google's reCAPTCHA service which requires API keys
+from Google.</p>
+<h2 id="getting-api-keys"><a class="header" href="#getting-api-keys">Getting API keys</a></h2>
+<ol>
+<li>Create a new site at <a href="https://www.google.com/recaptcha/admin/create">https://www.google.com/recaptcha/admin/create</a></li>
+<li>Set the label to anything you want</li>
+<li>Set the type to reCAPTCHA v2 using the &quot;I'm not a robot&quot; Checkbox option.
+This is the only type of captcha that works with Synapse.</li>
+<li>Add the public hostname for your server, as set in <code>public_baseurl</code>
+in <code>homeserver.yaml</code>, to the list of authorized domains. If you have not set
+<code>public_baseurl</code>, use <code>server_name</code>.</li>
+<li>Agree to the terms of service and submit.</li>
+<li>Copy your site key and secret key and add them to your <code>homeserver.yaml</code>
+configuration file
+<pre><code class="language-yaml">recaptcha_public_key: YOUR_SITE_KEY
+recaptcha_private_key: YOUR_SECRET_KEY
+</code></pre>
+</li>
+<li>Enable the CAPTCHA for new registrations
+<pre><code class="language-yaml">enable_registration_captcha: true
+</code></pre>
+</li>
+<li>Go to the settings page for the CAPTCHA you just created</li>
+<li>Uncheck the &quot;Verify the origin of reCAPTCHA solutions&quot; checkbox so that the
+captcha can be displayed in any client. If you do not disable this option then you
+must specify the domains of every client that is allowed to display the CAPTCHA.</li>
+</ol>
+<h2 id="configuring-ip-used-for-auth"><a class="header" href="#configuring-ip-used-for-auth">Configuring IP used for auth</a></h2>
+<p>The reCAPTCHA API requires that the IP address of the user who solved the
+CAPTCHA is sent. If the client is connecting through a proxy or load balancer,
+it may be required to use the <code>X-Forwarded-For</code> (XFF) header instead of the origin
+IP address. This can be configured using the <code>x_forwarded</code> directive in the
+listeners section of the <code>homeserver.yaml</code> configuration file.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="registering-an-application-service"><a class="header" href="#registering-an-application-service">Registering an Application Service</a></h1>
+<p>The registration of new application services depends on the homeserver used. 
+In synapse, you need to create a new configuration file for your AS and add it
+to the list specified under the <code>app_service_config_files</code> config
+option in your synapse config.</p>
+<p>For example:</p>
+<pre><code class="language-yaml">app_service_config_files:
+- /home/matrix/.synapse/&lt;your-AS&gt;.yaml
+</code></pre>
+<p>The format of the AS configuration file is as follows:</p>
+<pre><code class="language-yaml">url: &lt;base url of AS&gt;
+as_token: &lt;token AS will add to requests to HS&gt;
+hs_token: &lt;token HS will add to requests to AS&gt;
+sender_localpart: &lt;localpart of AS user&gt;
+namespaces:
+  users:  # List of users we're interested in
+    - exclusive: &lt;bool&gt;
+      regex: &lt;regex&gt;
+      group_id: &lt;group&gt;
+    - ...
+  aliases: []  # List of aliases we're interested in
+  rooms: [] # List of room ids we're interested in
+</code></pre>
+<p><code>exclusive</code>: If enabled, only this application service is allowed to register users in its namespace(s).
+<code>group_id</code>: All users of this application service are dynamically joined to this group. This is useful for e.g user organisation or flairs.</p>
+<p>See the <a href="https://matrix.org/docs/spec/application_service/unstable.html">spec</a> for further details on how application services work.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="server-notices"><a class="header" href="#server-notices">Server Notices</a></h1>
+<p>'Server Notices' are a new feature introduced in Synapse 0.30. They provide a
+channel whereby server administrators can send messages to users on the server.</p>
+<p>They are used as part of communication of the server polices (see
+<a href="consent_tracking.html">Consent Tracking</a>), however the intention is that
+they may also find a use for features such as &quot;Message of the day&quot;.</p>
+<p>This is a feature specific to Synapse, but it uses standard Matrix
+communication mechanisms, so should work with any Matrix client.</p>
+<h2 id="user-experience"><a class="header" href="#user-experience">User experience</a></h2>
+<p>When the user is first sent a server notice, they will get an invitation to a
+room (typically called 'Server Notices', though this is configurable in
+<code>homeserver.yaml</code>). They will be <strong>unable to reject</strong> this invitation -
+attempts to do so will receive an error.</p>
+<p>Once they accept the invitation, they will see the notice message in the room
+history; it will appear to have come from the 'server notices user' (see
+below).</p>
+<p>The user is prevented from sending any messages in this room by the power
+levels.</p>
+<p>Having joined the room, the user can leave the room if they want. Subsequent
+server notices will then cause a new room to be created.</p>
+<h2 id="synapse-configuration"><a class="header" href="#synapse-configuration">Synapse configuration</a></h2>
+<p>Server notices come from a specific user id on the server. Server
+administrators are free to choose the user id - something like <code>server</code> is
+suggested, meaning the notices will come from
+<code>@server:&lt;your_server_name&gt;</code>. Once the Server Notices user is configured, that
+user id becomes a special, privileged user, so administrators should ensure
+that <strong>it is not already allocated</strong>.</p>
+<p>In order to support server notices, it is necessary to add some configuration
+to the <code>homeserver.yaml</code> file. In particular, you should add a <code>server_notices</code>
+section, which should look like this:</p>
+<pre><code class="language-yaml">server_notices:
+   system_mxid_localpart: server
+   system_mxid_display_name: &quot;Server Notices&quot;
+   system_mxid_avatar_url: &quot;mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ&quot;
+   room_name: &quot;Server Notices&quot;
+</code></pre>
+<p>The only compulsory setting is <code>system_mxid_localpart</code>, which defines the user
+id of the Server Notices user, as above. <code>room_name</code> defines the name of the
+room which will be created.</p>
+<p><code>system_mxid_display_name</code> and <code>system_mxid_avatar_url</code> can be used to set the
+displayname and avatar of the Server Notices user.</p>
+<h2 id="sending-notices"><a class="header" href="#sending-notices">Sending notices</a></h2>
+<p>To send server notices to users you can use the
+<a href="admin_api/server_notices.html">admin_api</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="support-in-synapse-for-tracking-agreement-to-server-terms-and-conditions"><a class="header" href="#support-in-synapse-for-tracking-agreement-to-server-terms-and-conditions">Support in Synapse for tracking agreement to server terms and conditions</a></h1>
+<p>Synapse 0.30 introduces support for tracking whether users have agreed to the
+terms and conditions set by the administrator of a server - and blocking access
+to the server until they have.</p>
+<p>There are several parts to this functionality; each requires some specific
+configuration in <code>homeserver.yaml</code> to be enabled.</p>
+<p>Note that various parts of the configuation and this document refer to the
+&quot;privacy policy&quot;: agreement with a privacy policy is one particular use of this
+feature, but of course adminstrators can specify other terms and conditions
+unrelated to &quot;privacy&quot; per se.</p>
+<h2 id="collecting-policy-agreement-from-a-user"><a class="header" href="#collecting-policy-agreement-from-a-user">Collecting policy agreement from a user</a></h2>
+<p>Synapse can be configured to serve the user a simple policy form with an
+&quot;accept&quot; button. Clicking &quot;Accept&quot; records the user's acceptance in the
+database and shows a success page.</p>
+<p>To enable this, first create templates for the policy and success pages.
+These should be stored on the local filesystem.</p>
+<p>These templates use the <a href="http://jinja.pocoo.org">Jinja2</a> templating language,
+and <a href="https://github.com/matrix-org/synapse/tree/develop/docs/privacy_policy_templates/">docs/privacy_policy_templates</a>
+gives examples of the sort of thing that can be done.</p>
+<p>Note that the templates must be stored under a name giving the language of the
+template - currently this must always be <code>en</code> (for &quot;English&quot;);
+internationalisation support is intended for the future.</p>
+<p>The template for the policy itself should be versioned and named according to
+the version: for example <code>1.0.html</code>. The version of the policy which the user
+has agreed to is stored in the database.</p>
+<p>Once the templates are in place, make the following changes to <code>homeserver.yaml</code>:</p>
+<ol>
+<li>
+<p>Add a <code>user_consent</code> section, which should look like:</p>
+<pre><code class="language-yaml">user_consent:
+  template_dir: privacy_policy_templates
+  version: 1.0
+</code></pre>
+<p><code>template_dir</code> points to the directory containing the policy
+templates. <code>version</code> defines the version of the policy which will be served
+to the user. In the example above, Synapse will serve
+<code>privacy_policy_templates/en/1.0.html</code>.</p>
+</li>
+<li>
+<p>Add a <code>form_secret</code> setting at the top level:</p>
+<pre><code class="language-yaml">form_secret: &quot;&lt;unique secret&gt;&quot;
+</code></pre>
+<p>This should be set to an arbitrary secret string (try <code>pwgen -y 30</code> to
+generate suitable secrets).</p>
+<p>More on what this is used for below.</p>
+</li>
+<li>
+<p>Add <code>consent</code> wherever the <code>client</code> resource is currently enabled in the
+<code>listeners</code> configuration. For example:</p>
+<pre><code class="language-yaml">listeners:
+  - port: 8008
+    resources:
+      - names:
+        - client
+        - consent
+</code></pre>
+</li>
+</ol>
+<p>Finally, ensure that <code>jinja2</code> is installed. If you are using a virtualenv, this
+should be a matter of <code>pip install Jinja2</code>. On debian, try <code>apt-get install python-jinja2</code>.</p>
+<p>Once this is complete, and the server has been restarted, try visiting
+<code>https://&lt;server&gt;/_matrix/consent</code>. If correctly configured, this should give
+an error &quot;Missing string query parameter 'u'&quot;. It is now possible to manually
+construct URIs where users can give their consent.</p>
+<h3 id="enabling-consent-tracking-at-registration"><a class="header" href="#enabling-consent-tracking-at-registration">Enabling consent tracking at registration</a></h3>
+<ol>
+<li>
+<p>Add the following to your configuration:</p>
+<pre><code class="language-yaml">user_consent:
+  require_at_registration: true
+  policy_name: &quot;Privacy Policy&quot; # or whatever you'd like to call the policy
+</code></pre>
+</li>
+<li>
+<p>In your consent templates, make use of the <code>public_version</code> variable to
+see if an unauthenticated user is viewing the page. This is typically
+wrapped around the form that would be used to actually agree to the document:</p>
+<pre><code class="language-html">{% if not public_version %}
+  &lt;!-- The variables used here are only provided when the 'u' param is given to the homeserver --&gt;
+  &lt;form method=&quot;post&quot; action=&quot;consent&quot;&gt;
+    &lt;input type=&quot;hidden&quot; name=&quot;v&quot; value=&quot;{{version}}&quot;/&gt;
+    &lt;input type=&quot;hidden&quot; name=&quot;u&quot; value=&quot;{{user}}&quot;/&gt;
+    &lt;input type=&quot;hidden&quot; name=&quot;h&quot; value=&quot;{{userhmac}}&quot;/&gt;
+    &lt;input type=&quot;submit&quot; value=&quot;Sure thing!&quot;/&gt;
+  &lt;/form&gt;
+{% endif %}
+</code></pre>
+</li>
+<li>
+<p>Restart Synapse to apply the changes.</p>
+</li>
+</ol>
+<p>Visiting <code>https://&lt;server&gt;/_matrix/consent</code> should now give you a view of the privacy
+document. This is what users will be able to see when registering for accounts.</p>
+<h3 id="constructing-the-consent-uri"><a class="header" href="#constructing-the-consent-uri">Constructing the consent URI</a></h3>
+<p>It may be useful to manually construct the &quot;consent URI&quot; for a given user - for
+instance, in order to send them an email asking them to consent. To do this,
+take the base <code>https://&lt;server&gt;/_matrix/consent</code> URL and add the following
+query parameters:</p>
+<ul>
+<li>
+<p><code>u</code>: the user id of the user. This can either be a full MXID
+(<code>@user:server.com</code>) or just the localpart (<code>user</code>).</p>
+</li>
+<li>
+<p><code>h</code>: hex-encoded HMAC-SHA256 of <code>u</code> using the <code>form_secret</code> as a key. It is
+possible to calculate this on the commandline with something like:</p>
+<pre><code class="language-bash">echo -n '&lt;user&gt;' | openssl sha256 -hmac '&lt;form_secret&gt;'
+</code></pre>
+<p>This should result in a URI which looks something like:
+<code>https://&lt;server&gt;/_matrix/consent?u=&lt;user&gt;&amp;h=68a152465a4d...</code>.</p>
+</li>
+</ul>
+<p>Note that not providing a <code>u</code> parameter will be interpreted as wanting to view
+the document from an unauthenticated perspective, such as prior to registration.
+Therefore, the <code>h</code> parameter is not required in this scenario. To enable this
+behaviour, set <code>require_at_registration</code> to <code>true</code> in your <code>user_consent</code> config.</p>
+<h2 id="sending-users-a-server-notice-asking-them-to-agree-to-the-policy"><a class="header" href="#sending-users-a-server-notice-asking-them-to-agree-to-the-policy">Sending users a server notice asking them to agree to the policy</a></h2>
+<p>It is possible to configure Synapse to send a <a href="server_notices.html">server
+notice</a> to anybody who has not yet agreed to the current
+version of the policy. To do so:</p>
+<ul>
+<li>
+<p>ensure that the consent resource is configured, as in the previous section</p>
+</li>
+<li>
+<p>ensure that server notices are configured, as in <a href="server_notices.html">the server notice documentation</a>.</p>
+</li>
+<li>
+<p>Add <code>server_notice_content</code> under <code>user_consent</code> in <code>homeserver.yaml</code>. For
+example:</p>
+<pre><code class="language-yaml">user_consent:
+  server_notice_content:
+    msgtype: m.text
+    body: &gt;-
+      Please give your consent to the privacy policy at %(consent_uri)s.
+</code></pre>
+<p>Synapse automatically replaces the placeholder <code>%(consent_uri)s</code> with the
+consent uri for that user.</p>
+</li>
+<li>
+<p>ensure that <code>public_baseurl</code> is set in <code>homeserver.yaml</code>, and gives the base
+URI that clients use to connect to the server. (It is used to construct
+<code>consent_uri</code> in the server notice.)</p>
+</li>
+</ul>
+<h2 id="blocking-users-from-using-the-server-until-they-agree-to-the-policy"><a class="header" href="#blocking-users-from-using-the-server-until-they-agree-to-the-policy">Blocking users from using the server until they agree to the policy</a></h2>
+<p>Synapse can be configured to block any attempts to join rooms or send messages
+until the user has given their agreement to the policy. (Joining the server
+notices room is exempted from this).</p>
+<p>To enable this, add <code>block_events_error</code> under <code>user_consent</code>. For example:</p>
+<pre><code class="language-yaml">user_consent:
+  block_events_error: &gt;-
+    You can't send any messages until you consent to the privacy policy at
+    %(consent_uri)s.
+</code></pre>
+<p>Synapse automatically replaces the placeholder <code>%(consent_uri)s</code> with the
+consent uri for that user.</p>
+<p>ensure that <code>public_baseurl</code> is set in <code>homeserver.yaml</code>, and gives the base
+URI that clients use to connect to the server. (It is used to construct
+<code>consent_uri</code> in the error.)</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="url-previews-1"><a class="header" href="#url-previews-1">URL Previews</a></h1>
+<p>The <code>GET /_matrix/media/r0/preview_url</code> endpoint provides a generic preview API
+for URLs which outputs <a href="https://ogp.me/">Open Graph</a> responses (with some Matrix
+specific additions).</p>
+<p>This does have trade-offs compared to other designs:</p>
+<ul>
+<li>Pros:
+<ul>
+<li>Simple and flexible; can be used by any clients at any point</li>
+</ul>
+</li>
+<li>Cons:
+<ul>
+<li>If each homeserver provides one of these independently, all the HSes in a
+room may needlessly DoS the target URI</li>
+<li>The URL metadata must be stored somewhere, rather than just using Matrix
+itself to store the media.</li>
+<li>Matrix cannot be used to distribute the metadata between homeservers.</li>
+</ul>
+</li>
+</ul>
+<p>When Synapse is asked to preview a URL it does the following:</p>
+<ol>
+<li>Checks against a URL blacklist (defined as <code>url_preview_url_blacklist</code> in the
+config).</li>
+<li>Checks the in-memory cache by URLs and returns the result if it exists. (This
+is also used to de-duplicate processing of multiple in-flight requests at once.)</li>
+<li>Kicks off a background process to generate a preview:
+<ol>
+<li>Checks the database cache by URL and timestamp and returns the result if it
+has not expired and was successful (a 2xx return code).</li>
+<li>Checks if the URL matches an <a href="https://oembed.com/">oEmbed</a> pattern. If it
+does, update the URL to download.</li>
+<li>Downloads the URL and stores it into a file via the media storage provider
+and saves the local media metadata.</li>
+<li>If the media is an image:
+<ol>
+<li>Generates thumbnails.</li>
+<li>Generates an Open Graph response based on image properties.</li>
+</ol>
+</li>
+<li>If the media is HTML:
+<ol>
+<li>Decodes the HTML via the stored file.</li>
+<li>Generates an Open Graph response from the HTML.</li>
+<li>If a JSON oEmbed URL was found in the HTML via autodiscovery:
+<ol>
+<li>Downloads the URL and stores it into a file via the media storage provider
+and saves the local media metadata.</li>
+<li>Convert the oEmbed response to an Open Graph response.</li>
+<li>Override any Open Graph data from the HTML with data from oEmbed.</li>
+</ol>
+</li>
+<li>If an image exists in the Open Graph response:
+<ol>
+<li>Downloads the URL and stores it into a file via the media storage
+provider and saves the local media metadata.</li>
+<li>Generates thumbnails.</li>
+<li>Updates the Open Graph response based on image properties.</li>
+</ol>
+</li>
+</ol>
+</li>
+<li>If the media is JSON and an oEmbed URL was found:
+<ol>
+<li>Convert the oEmbed response to an Open Graph response.</li>
+<li>If a thumbnail or image is in the oEmbed response:
+<ol>
+<li>Downloads the URL and stores it into a file via the media storage
+provider and saves the local media metadata.</li>
+<li>Generates thumbnails.</li>
+<li>Updates the Open Graph response based on image properties.</li>
+</ol>
+</li>
+</ol>
+</li>
+<li>Stores the result in the database cache.</li>
+</ol>
+</li>
+<li>Returns the result.</li>
+</ol>
+<p>The in-memory cache expires after 1 hour.</p>
+<p>Expired entries in the database cache (and their associated media files) are
+deleted every 10 seconds. The default expiration time is 1 hour from download.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="user-directory-api-implementation"><a class="header" href="#user-directory-api-implementation">User Directory API Implementation</a></h1>
+<p>The user directory is currently maintained based on the 'visible' users
+on this particular server - i.e. ones which your account shares a room with, or
+who are present in a publicly viewable room present on the server.</p>
+<p>The directory info is stored in various tables, which can (typically after
+DB corruption) get stale or out of sync. If this happens, for now the
+solution to fix it is to use the <a href="usage/administration/admin_api/background_updates.html#run">admin API</a>
+and execute the job <code>regenerate_directory</code>. This should then start a background task to
+flush the current tables and regenerate the directory.</p>
+<h2 id="data-model"><a class="header" href="#data-model">Data model</a></h2>
+<p>There are five relevant tables that collectively form the &quot;user directory&quot;.
+Three of them track a master list of all the users we could search for.
+The last two (collectively called the &quot;search tables&quot;) track who can
+see who.</p>
+<p>From all of these tables we exclude three types of local user:</p>
+<ul>
+<li>support users</li>
+<li>appservice users</li>
+<li>deactivated users</li>
+</ul>
+<ul>
+<li>
+<p><code>user_directory</code>. This contains the user_id, display name and avatar we'll
+return when you search the directory.</p>
+<ul>
+<li>Because there's only one directory entry per user, it's important that we only
+ever put publicly visible names here. Otherwise we might leak a private
+nickname or avatar used in a private room.</li>
+<li>Indexed on rooms. Indexed on users.</li>
+</ul>
+</li>
+<li>
+<p><code>user_directory_search</code>. To be joined to <code>user_directory</code>. It contains an extra
+column that enables full text search based on user ids and display names.
+Different schemas for SQLite and Postgres with different code paths to match.</p>
+<ul>
+<li>Indexed on the full text search data. Indexed on users.</li>
+</ul>
+</li>
+<li>
+<p><code>user_directory_stream_pos</code>. When the initial background update to populate
+the directory is complete, we record a stream position here. This indicates
+that synapse should now listen for room changes and incrementally update
+the directory where necessary.</p>
+</li>
+<li>
+<p><code>users_in_public_rooms</code>. Contains associations between users and the public rooms they're in.
+Used to determine which users are in public rooms and should be publicly visible in the directory.</p>
+</li>
+<li>
+<p><code>users_who_share_private_rooms</code>. Rows are triples <code>(L, M, room id)</code> where <code>L</code>
+is a local user and <code>M</code> is a local or remote user. <code>L</code> and <code>M</code> should be
+different, but this isn't enforced by a constraint.</p>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="message-retention-policies"><a class="header" href="#message-retention-policies">Message retention policies</a></h1>
+<p>Synapse admins can enable support for message retention policies on
+their homeserver. Message retention policies exist at a room level,
+follow the semantics described in
+<a href="https://github.com/matrix-org/matrix-doc/blob/matthew/msc1763/proposals/1763-configurable-retention-periods.md">MSC1763</a>,
+and allow server and room admins to configure how long messages should
+be kept in a homeserver's database before being purged from it.
+<strong>Please note that, as this feature isn't part of the Matrix
+specification yet, this implementation is to be considered as
+experimental.</strong> </p>
+<p>A message retention policy is mainly defined by its <code>max_lifetime</code>
+parameter, which defines how long a message can be kept around after
+it was sent to the room. If a room doesn't have a message retention
+policy, and there's no default one for a given server, then no message
+sent in that room is ever purged on that server.</p>
+<p>MSC1763 also specifies semantics for a <code>min_lifetime</code> parameter which
+defines the amount of time after which an event <em>can</em> get purged (after
+it was sent to the room), but Synapse doesn't currently support it
+beyond registering it.</p>
+<p>Both <code>max_lifetime</code> and <code>min_lifetime</code> are optional parameters.</p>
+<p>Note that message retention policies don't apply to state events.</p>
+<p>Once an event reaches its expiry date (defined as the time it was sent
+plus the value for <code>max_lifetime</code> in the room), two things happen:</p>
+<ul>
+<li>Synapse stops serving the event to clients via any endpoint.</li>
+<li>The message gets picked up by the next purge job (see the &quot;Purge jobs&quot;
+section) and is removed from Synapse's database.</li>
+</ul>
+<p>Since purge jobs don't run continuously, this means that an event might
+stay in a server's database for longer than the value for <code>max_lifetime</code>
+in the room would allow, though hidden from clients.</p>
+<p>Similarly, if a server (with support for message retention policies
+enabled) receives from another server an event that should have been
+purged according to its room's policy, then the receiving server will
+process and store that event until it's picked up by the next purge job,
+though it will always hide it from clients.</p>
+<p>Synapse requires at least one message in each room, so it will never
+delete the last message in a room. It will, however, hide it from
+clients.</p>
+<h2 id="server-configuration"><a class="header" href="#server-configuration">Server configuration</a></h2>
+<p>Support for this feature can be enabled and configured in the
+<code>retention</code> section of the Synapse configuration file (see the
+<a href="https://github.com/matrix-org/synapse/blob/v1.36.0/docs/sample_config.yaml#L451-L518">sample file</a>).</p>
+<p>To enable support for message retention policies, set the setting
+<code>enabled</code> in this section to <code>true</code>.</p>
+<h3 id="default-policy"><a class="header" href="#default-policy">Default policy</a></h3>
+<p>A default message retention policy is a policy defined in Synapse's
+configuration that is used by Synapse for every room that doesn't have a
+message retention policy configured in its state. This allows server
+admins to ensure that messages are never kept indefinitely in a server's
+database. </p>
+<p>A default policy can be defined as such, in the <code>retention</code> section of
+the configuration file:</p>
+<pre><code class="language-yaml">default_policy:
+  min_lifetime: 1d
+  max_lifetime: 1y
+</code></pre>
+<p>Here, <code>min_lifetime</code> and <code>max_lifetime</code> have the same meaning and level
+of support as previously described. They can be expressed either as a
+duration (using the units <code>s</code> (seconds), <code>m</code> (minutes), <code>h</code> (hours),
+<code>d</code> (days), <code>w</code> (weeks) and <code>y</code> (years)) or as a number of milliseconds.</p>
+<h3 id="purge-jobs"><a class="header" href="#purge-jobs">Purge jobs</a></h3>
+<p>Purge jobs are the jobs that Synapse runs in the background to purge
+expired events from the database. They are only run if support for
+message retention policies is enabled in the server's configuration. If
+no configuration for purge jobs is configured by the server admin,
+Synapse will use a default configuration, which is described in the
+<a href="https://github.com/matrix-org/synapse/blob/v1.36.0/docs/sample_config.yaml#L451-L518">sample configuration file</a>.</p>
+<p>Some server admins might want a finer control on when events are removed
+depending on an event's room's policy. This can be done by setting the
+<code>purge_jobs</code> sub-section in the <code>retention</code> section of the configuration
+file. An example of such configuration could be:</p>
+<pre><code class="language-yaml">purge_jobs:
+  - longest_max_lifetime: 3d
+    interval: 12h
+  - shortest_max_lifetime: 3d
+    longest_max_lifetime: 1w
+    interval: 1d
+  - shortest_max_lifetime: 1w
+    interval: 2d
+</code></pre>
+<p>In this example, we define three jobs:</p>
+<ul>
+<li>one that runs twice a day (every 12 hours) and purges events in rooms
+which policy's <code>max_lifetime</code> is lower or equal to 3 days.</li>
+<li>one that runs once a day and purges events in rooms which policy's
+<code>max_lifetime</code> is between 3 days and a week.</li>
+<li>one that runs once every 2 days and purges events in rooms which
+policy's <code>max_lifetime</code> is greater than a week.</li>
+</ul>
+<p>Note that this example is tailored to show different configurations and
+features slightly more jobs than it's probably necessary (in practice, a
+server admin would probably consider it better to replace the two last
+jobs with one that runs once a day and handles rooms which which
+policy's <code>max_lifetime</code> is greater than 3 days).</p>
+<p>Keep in mind, when configuring these jobs, that a purge job can become
+quite heavy on the server if it targets many rooms, therefore prefer
+having jobs with a low interval that target a limited set of rooms. Also
+make sure to include a job with no minimum and one with no maximum to
+make sure your configuration handles every policy.</p>
+<p>As previously mentioned in this documentation, while a purge job that
+runs e.g. every day means that an expired event might stay in the
+database for up to a day after its expiry, Synapse hides expired events
+from clients as soon as they expire, so the event is not visible to
+local users between its expiry date and the moment it gets purged from
+the server's database.</p>
+<h3 id="lifetime-limits"><a class="header" href="#lifetime-limits">Lifetime limits</a></h3>
+<p>Server admins can set limits on the values of <code>max_lifetime</code> to use when
+purging old events in a room. These limits can be defined as such in the
+<code>retention</code> section of the configuration file:</p>
+<pre><code class="language-yaml">allowed_lifetime_min: 1d
+allowed_lifetime_max: 1y
+</code></pre>
+<p>The limits are considered when running purge jobs. If necessary, the
+effective value of <code>max_lifetime</code> will be brought between
+<code>allowed_lifetime_min</code> and <code>allowed_lifetime_max</code> (inclusive).
+This means that, if the value of <code>max_lifetime</code> defined in the room's state
+is lower than <code>allowed_lifetime_min</code>, the value of <code>allowed_lifetime_min</code>
+will be used instead. Likewise, if the value of <code>max_lifetime</code> is higher
+than <code>allowed_lifetime_max</code>, the value of <code>allowed_lifetime_max</code> will be
+used instead.</p>
+<p>In the example above, we ensure Synapse never deletes events that are less
+than one day old, and that it always deletes events that are over a year
+old.</p>
+<p>If a default policy is set, and its <code>max_lifetime</code> value is lower than
+<code>allowed_lifetime_min</code> or higher than <code>allowed_lifetime_max</code>, the same
+process applies.</p>
+<p>Both parameters are optional; if one is omitted Synapse won't use it to
+adjust the effective value of <code>max_lifetime</code>.</p>
+<p>Like other settings in this section, these parameters can be expressed
+either as a duration or as a number of milliseconds.</p>
+<h2 id="room-configuration"><a class="header" href="#room-configuration">Room configuration</a></h2>
+<p>To configure a room's message retention policy, a room's admin or
+moderator needs to send a state event in that room with the type
+<code>m.room.retention</code> and the following content:</p>
+<pre><code class="language-json">{
+    &quot;max_lifetime&quot;: ...
+}
+</code></pre>
+<p>In this event's content, the <code>max_lifetime</code> parameter has the same
+meaning as previously described, and needs to be expressed in
+milliseconds. The event's content can also include a <code>min_lifetime</code>
+parameter, which has the same meaning and limited support as previously
+described.</p>
+<p>Note that over every server in the room, only the ones with support for
+message retention policies will actually remove expired events. This
+support is currently not enabled by default in Synapse.</p>
+<h2 id="note-on-reclaiming-disk-space"><a class="header" href="#note-on-reclaiming-disk-space">Note on reclaiming disk space</a></h2>
+<p>While purge jobs actually delete data from the database, the disk space
+used by the database might not decrease immediately on the database's
+host. However, even though the database engine won't free up the disk
+space, it will start writing new data into where the purged data was.</p>
+<p>If you want to reclaim the freed disk space anyway and return it to the
+operating system, the server admin needs to run <code>VACUUM FULL;</code> (or
+<code>VACUUM;</code> for SQLite databases) on Synapse's database (see the related
+<a href="https://www.postgresql.org/docs/current/sql-vacuum.html">PostgreSQL documentation</a>).</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="modules"><a class="header" href="#modules">Modules</a></h1>
+<p>Synapse supports extending its functionality by configuring external modules.</p>
+<p><strong>Note</strong>: When using third-party modules, you effectively allow someone else to run
+custom code on your Synapse homeserver. Server admins are encouraged to verify the
+provenance of the modules they use on their homeserver and make sure the modules aren't
+running malicious code on their instance.</p>
+<h2 id="using-modules"><a class="header" href="#using-modules">Using modules</a></h2>
+<p>To use a module on Synapse, add it to the <code>modules</code> section of the configuration file:</p>
+<pre><code class="language-yaml">modules:
+  - module: my_super_module.MySuperClass
+    config:
+      do_thing: true
+  - module: my_other_super_module.SomeClass
+    config: {}
+</code></pre>
+<p>Each module is defined by a path to a Python class as well as a configuration. This
+information for a given module should be available in the module's own documentation.</p>
+<h2 id="using-multiple-modules"><a class="header" href="#using-multiple-modules">Using multiple modules</a></h2>
+<p>The order in which modules are listed in this section is important. When processing an
+action that can be handled by several modules, Synapse will always prioritise the module
+that appears first (i.e. is the highest in the list). This means:</p>
+<ul>
+<li>If several modules register the same callback, the callback registered by the module
+that appears first is used.</li>
+<li>If several modules try to register a handler for the same HTTP path, only the handler
+registered by the module that appears first is used. Handlers registered by the other
+module(s) are ignored and Synapse will log a warning message about them.</li>
+</ul>
+<p>Note that Synapse doesn't allow multiple modules implementing authentication checkers via
+the password auth provider feature for the same login type with different fields. If this
+happens, Synapse will refuse to start.</p>
+<h2 id="current-status"><a class="header" href="#current-status">Current status</a></h2>
+<p>We are currently in the process of migrating module interfaces to this system. While some
+interfaces might be compatible with it, others still require configuring modules in
+another part of Synapse's configuration file.</p>
+<p>Currently, only the following pre-existing interfaces are compatible with this new system:</p>
+<ul>
+<li>spam checker</li>
+<li>third-party rules</li>
+<li>presence router</li>
+<li>password auth providers</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="writing-a-module"><a class="header" href="#writing-a-module">Writing a module</a></h1>
+<p>A module is a Python class that uses Synapse's module API to interact with the
+homeserver. It can register callbacks that Synapse will call on specific operations, as
+well as web resources to attach to Synapse's web server.</p>
+<p>When instantiated, a module is given its parsed configuration as well as an instance of
+the <code>synapse.module_api.ModuleApi</code> class. The configuration is a dictionary, and is
+either the output of the module's <code>parse_config</code> static method (see below), or the
+configuration associated with the module in Synapse's configuration file.</p>
+<p>See the documentation for the <code>ModuleApi</code> class
+<a href="https://github.com/matrix-org/synapse/blob/master/synapse/module_api/__init__.py">here</a>.</p>
+<h2 id="when-synapse-runs-with-several-modules-configured"><a class="header" href="#when-synapse-runs-with-several-modules-configured">When Synapse runs with several modules configured</a></h2>
+<p>If Synapse is running with other modules configured, the order each module appears in
+within the <code>modules</code> section of the Synapse configuration file might restrict what it can
+or cannot register. See <a href="modules/index.html#using-multiple-modules">this section</a> for more
+information.</p>
+<p>On top of the rules listed in the link above, if a callback returns a value that should
+cause the current operation to fail (e.g. if a callback checking an event returns with a
+value that should cause the event to be denied), Synapse will fail the operation and
+ignore any subsequent callbacks that should have been run after this one.</p>
+<p>The documentation for each callback mentions how Synapse behaves when
+multiple modules implement it.</p>
+<h2 id="handling-the-modules-configuration"><a class="header" href="#handling-the-modules-configuration">Handling the module's configuration</a></h2>
+<p>A module can implement the following static method:</p>
+<pre><code class="language-python">@staticmethod
+def parse_config(config: dict) -&gt; dict
+</code></pre>
+<p>This method is given a dictionary resulting from parsing the YAML configuration for the
+module. It may modify it (for example by parsing durations expressed as strings (e.g.
+&quot;5d&quot;) into milliseconds, etc.), and return the modified dictionary. It may also verify
+that the configuration is correct, and raise an instance of
+<code>synapse.module_api.errors.ConfigError</code> if not.</p>
+<h2 id="registering-a-web-resource"><a class="header" href="#registering-a-web-resource">Registering a web resource</a></h2>
+<p>Modules can register web resources onto Synapse's web server using the following module
+API method:</p>
+<pre><code class="language-python">def ModuleApi.register_web_resource(path: str, resource: IResource) -&gt; None
+</code></pre>
+<p>The path is the full absolute path to register the resource at. For example, if you
+register a resource for the path <code>/_synapse/client/my_super_module/say_hello</code>, Synapse
+will serve it at <code>http(s)://[HS_URL]/_synapse/client/my_super_module/say_hello</code>. Note
+that Synapse does not allow registering resources for several sub-paths in the <code>/_matrix</code>
+namespace (such as anything under <code>/_matrix/client</code> for example). It is strongly
+recommended that modules register their web resources under the <code>/_synapse/client</code>
+namespace.</p>
+<p>The provided resource is a Python class that implements Twisted's <a href="https://twistedmatrix.com/documents/current/api/twisted.web.resource.IResource.html">IResource</a>
+interface (such as <a href="https://twistedmatrix.com/documents/current/api/twisted.web.resource.Resource.html">Resource</a>).</p>
+<p>Only one resource can be registered for a given path. If several modules attempt to
+register a resource for the same path, the module that appears first in Synapse's
+configuration file takes priority.</p>
+<p>Modules <strong>must</strong> register their web resources in their <code>__init__</code> method.</p>
+<h2 id="registering-a-callback"><a class="header" href="#registering-a-callback">Registering a callback</a></h2>
+<p>Modules can use Synapse's module API to register callbacks. Callbacks are functions that
+Synapse will call when performing specific actions. Callbacks must be asynchronous (unless
+specified otherwise), and are split in categories. A single module may implement callbacks
+from multiple categories, and is under no obligation to implement all callbacks from the
+categories it registers callbacks for.</p>
+<p>Modules can register callbacks using one of the module API's <code>register_[...]_callbacks</code>
+methods. The callback functions are passed to these methods as keyword arguments, with
+the callback name as the argument name and the function as its value. A
+<code>register_[...]_callbacks</code> method exists for each category.</p>
+<p>Callbacks for each category can be found on their respective page of the
+<a href="https://matrix-org.github.io/synapse">Synapse documentation website</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="spam-checker-callbacks"><a class="header" href="#spam-checker-callbacks">Spam checker callbacks</a></h1>
+<p>Spam checker callbacks allow module developers to implement spam mitigation actions for
+Synapse instances. Spam checker callbacks can be registered using the module API's
+<code>register_spam_checker_callbacks</code> method.</p>
+<h2 id="callbacks"><a class="header" href="#callbacks">Callbacks</a></h2>
+<p>The available spam checker callbacks are:</p>
+<h3 id="check_event_for_spam"><a class="header" href="#check_event_for_spam"><code>check_event_for_spam</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def check_event_for_spam(event: &quot;synapse.events.EventBase&quot;) -&gt; Union[bool, str]
+</code></pre>
+<p>Called when receiving an event from a client or via federation. The callback must return
+either:</p>
+<ul>
+<li>an error message string, to indicate the event must be rejected because of spam and 
+give a rejection reason to forward to clients;</li>
+<li>the boolean <code>True</code>, to indicate that the event is spammy, but not provide further details; or</li>
+<li>the booelan <code>False</code>, to indicate that the event is not considered spammy.</li>
+</ul>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_join_room"><a class="header" href="#user_may_join_room"><code>user_may_join_room</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def user_may_join_room(user: str, room: str, is_invited: bool) -&gt; bool
+</code></pre>
+<p>Called when a user is trying to join a room. The module must return a <code>bool</code> to indicate
+whether the user can join the room. Return <code>False</code> to prevent the user from joining the
+room; otherwise return <code>True</code> to permit the joining.</p>
+<p>The user is represented by their Matrix user ID (e.g.
+<code>@alice:example.com</code>) and the room is represented by its Matrix ID (e.g.
+<code>!room:example.com</code>). The module is also given a boolean to indicate whether the user
+currently has a pending invite in the room.</p>
+<p>This callback isn't called if the join is performed by a server administrator, or in the
+context of a room creation.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_invite"><a class="header" href="#user_may_invite"><code>user_may_invite</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def user_may_invite(inviter: str, invitee: str, room_id: str) -&gt; bool
+</code></pre>
+<p>Called when processing an invitation. The module must return a <code>bool</code> indicating whether
+the inviter can invite the invitee to the given room. Both inviter and invitee are
+represented by their Matrix user ID (e.g. <code>@alice:example.com</code>). Return <code>False</code> to prevent
+the invitation; otherwise return <code>True</code> to permit it.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_send_3pid_invite"><a class="header" href="#user_may_send_3pid_invite"><code>user_may_send_3pid_invite</code></a></h3>
+<p><em>First introduced in Synapse v1.45.0</em></p>
+<pre><code class="language-python">async def user_may_send_3pid_invite(
+    inviter: str,
+    medium: str,
+    address: str,
+    room_id: str,
+) -&gt; bool
+</code></pre>
+<p>Called when processing an invitation using a third-party identifier (also called a 3PID,
+e.g. an email address or a phone number). The module must return a <code>bool</code> indicating
+whether the inviter can invite the invitee to the given room. Return <code>False</code> to prevent
+the invitation; otherwise return <code>True</code> to permit it.</p>
+<p>The inviter is represented by their Matrix user ID (e.g. <code>@alice:example.com</code>), and the
+invitee is represented by its medium (e.g. &quot;email&quot;) and its address
+(e.g. <code>alice@example.com</code>). See <a href="https://matrix.org/docs/spec/appendices#pid-types">the Matrix specification</a>
+for more information regarding third-party identifiers.</p>
+<p>For example, a call to this callback to send an invitation to the email address
+<code>alice@example.com</code> would look like this:</p>
+<pre><code class="language-python">await user_may_send_3pid_invite(
+    &quot;@bob:example.com&quot;,  # The inviter's user ID
+    &quot;email&quot;,  # The medium of the 3PID to invite
+    &quot;alice@example.com&quot;,  # The address of the 3PID to invite
+    &quot;!some_room:example.com&quot;,  # The ID of the room to send the invite into
+)
+</code></pre>
+<p><strong>Note</strong>: If the third-party identifier is already associated with a matrix user ID,
+<a href="modules/spam_checker_callbacks.html#user_may_invite"><code>user_may_invite</code></a> will be used instead.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_create_room"><a class="header" href="#user_may_create_room"><code>user_may_create_room</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def user_may_create_room(user: str) -&gt; bool
+</code></pre>
+<p>Called when processing a room creation request. The module must return a <code>bool</code> indicating
+whether the given user (represented by their Matrix user ID) is allowed to create a room.
+Return <code>False</code> to prevent room creation; otherwise return <code>True</code> to permit it.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_create_room_alias"><a class="header" href="#user_may_create_room_alias"><code>user_may_create_room_alias</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def user_may_create_room_alias(user: str, room_alias: &quot;synapse.types.RoomAlias&quot;) -&gt; bool
+</code></pre>
+<p>Called when trying to associate an alias with an existing room. The module must return a
+<code>bool</code> indicating whether the given user (represented by their Matrix user ID) is allowed
+to set the given alias. Return <code>False</code> to prevent the alias creation; otherwise return 
+<code>True</code> to permit it.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="user_may_publish_room"><a class="header" href="#user_may_publish_room"><code>user_may_publish_room</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def user_may_publish_room(user: str, room_id: str) -&gt; bool
+</code></pre>
+<p>Called when trying to publish a room to the homeserver's public rooms directory. The
+module must return a <code>bool</code> indicating whether the given user (represented by their
+Matrix user ID) is allowed to publish the given room. Return <code>False</code> to prevent the
+room from being published; otherwise return <code>True</code> to permit its publication.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="check_username_for_spam"><a class="header" href="#check_username_for_spam"><code>check_username_for_spam</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def check_username_for_spam(user_profile: synapse.module_api.UserProfile) -&gt; bool
+</code></pre>
+<p>Called when computing search results in the user directory. The module must return a
+<code>bool</code> indicating whether the given user should be excluded from user directory 
+searches. Return <code>True</code> to indicate that the user is spammy and exclude them from 
+search results; otherwise return <code>False</code>.</p>
+<p>The profile is represented as a dictionary with the following keys:</p>
+<ul>
+<li><code>user_id: str</code>. The Matrix ID for this user.</li>
+<li><code>display_name: Optional[str]</code>. The user's display name, or <code>None</code> if this user
+has not set a display name.</li>
+<li><code>avatar_url: Optional[str]</code>. The <code>mxc://</code> URL to the user's avatar, or <code>None</code>
+if this user has not set an avatar.</li>
+</ul>
+<p>The module is given a copy of the original dictionary, so modifying it from within the
+module cannot modify a user's profile when included in user directory search results.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="check_registration_for_spam"><a class="header" href="#check_registration_for_spam"><code>check_registration_for_spam</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def check_registration_for_spam(
+    email_threepid: Optional[dict],
+    username: Optional[str],
+    request_info: Collection[Tuple[str, str]],
+    auth_provider_id: Optional[str] = None,
+) -&gt; &quot;synapse.spam_checker_api.RegistrationBehaviour&quot;
+</code></pre>
+<p>Called when registering a new user. The module must return a <code>RegistrationBehaviour</code>
+indicating whether the registration can go through or must be denied, or whether the user
+may be allowed to register but will be shadow banned.</p>
+<p>The arguments passed to this callback are:</p>
+<ul>
+<li><code>email_threepid</code>: The email address used for registering, if any.</li>
+<li><code>username</code>: The username the user would like to register. Can be <code>None</code>, meaning that
+Synapse will generate one later.</li>
+<li><code>request_info</code>: A collection of tuples, which first item is a user agent, and which
+second item is an IP address. These user agents and IP addresses are the ones that were
+used during the registration process.</li>
+<li><code>auth_provider_id</code>: The identifier of the SSO authentication provider, if any.</li>
+</ul>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>RegistrationBehaviour.ALLOW</code>, Synapse falls through to the next one.
+The value of the first callback that does not return <code>RegistrationBehaviour.ALLOW</code> will
+be used. If this happens, Synapse will not call any of the subsequent implementations of
+this callback.</p>
+<h3 id="check_media_file_for_spam"><a class="header" href="#check_media_file_for_spam"><code>check_media_file_for_spam</code></a></h3>
+<p><em>First introduced in Synapse v1.37.0</em></p>
+<pre><code class="language-python">async def check_media_file_for_spam(
+    file_wrapper: &quot;synapse.rest.media.v1.media_storage.ReadableFileWrapper&quot;,
+    file_info: &quot;synapse.rest.media.v1._base.FileInfo&quot;,
+) -&gt; bool
+</code></pre>
+<p>Called when storing a local or remote file. The module must return a <code>bool</code> indicating
+whether the given file should be excluded from the homeserver's media store. Return
+<code>True</code> to prevent this file from being stored; otherwise return <code>False</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h2 id="example"><a class="header" href="#example">Example</a></h2>
+<p>The example below is a module that implements the spam checker callback
+<code>check_event_for_spam</code> to deny any message sent by users whose Matrix user IDs are
+mentioned in a configured list, and registers a web resource to the path
+<code>/_synapse/client/list_spam_checker/is_evil</code> that returns a JSON object indicating
+whether the provided user appears in that list.</p>
+<pre><code class="language-python">import json
+from typing import Union
+
+from twisted.web.resource import Resource
+from twisted.web.server import Request
+
+from synapse.module_api import ModuleApi
+
+
+class IsUserEvilResource(Resource):
+    def __init__(self, config):
+        super(IsUserEvilResource, self).__init__()
+        self.evil_users = config.get(&quot;evil_users&quot;) or []
+
+    def render_GET(self, request: Request):
+        user = request.args.get(b&quot;user&quot;)[0].decode()
+        request.setHeader(b&quot;Content-Type&quot;, b&quot;application/json&quot;)
+        return json.dumps({&quot;evil&quot;: user in self.evil_users}).encode()
+
+
+class ListSpamChecker:
+    def __init__(self, config: dict, api: ModuleApi):
+        self.api = api
+        self.evil_users = config.get(&quot;evil_users&quot;) or []
+
+        self.api.register_spam_checker_callbacks(
+            check_event_for_spam=self.check_event_for_spam,
+        )
+
+        self.api.register_web_resource(
+            path=&quot;/_synapse/client/list_spam_checker/is_evil&quot;,
+            resource=IsUserEvilResource(config),
+        )
+
+    async def check_event_for_spam(self, event: &quot;synapse.events.EventBase&quot;) -&gt; Union[bool, str]:
+        return event.sender not in self.evil_users
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="third-party-rules-callbacks"><a class="header" href="#third-party-rules-callbacks">Third party rules callbacks</a></h1>
+<p>Third party rules callbacks allow module developers to add extra checks to verify the
+validity of incoming events. Third party event rules callbacks can be registered using
+the module API's <code>register_third_party_rules_callbacks</code> method.</p>
+<h2 id="callbacks-1"><a class="header" href="#callbacks-1">Callbacks</a></h2>
+<p>The available third party rules callbacks are:</p>
+<h3 id="check_event_allowed"><a class="header" href="#check_event_allowed"><code>check_event_allowed</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def check_event_allowed(
+    event: &quot;synapse.events.EventBase&quot;,
+    state_events: &quot;synapse.types.StateMap&quot;,
+) -&gt; Tuple[bool, Optional[dict]]
+</code></pre>
+<p><strong><span style="color:red">
+This callback is very experimental and can and will break without notice. Module developers
+are encouraged to implement <code>check_event_for_spam</code> from the spam checker category instead.
+</span></strong></p>
+<p>Called when processing any incoming event, with the event and a <code>StateMap</code>
+representing the current state of the room the event is being sent into. A <code>StateMap</code> is
+a dictionary that maps tuples containing an event type and a state key to the
+corresponding state event. For example retrieving the room's <code>m.room.create</code> event from
+the <code>state_events</code> argument would look like this: <code>state_events.get((&quot;m.room.create&quot;, &quot;&quot;))</code>.
+The module must return a boolean indicating whether the event can be allowed.</p>
+<p>Note that this callback function processes incoming events coming via federation
+traffic (on top of client traffic). This means denying an event might cause the local
+copy of the room's history to diverge from that of remote servers. This may cause
+federation issues in the room. It is strongly recommended to only deny events using this
+callback function if the sender is a local user, or in a private federation in which all
+servers are using the same module, with the same configuration.</p>
+<p>If the boolean returned by the module is <code>True</code>, it may also tell Synapse to replace the
+event with new data by returning the new event's data as a dictionary. In order to do
+that, it is recommended the module calls <code>event.get_dict()</code> to get the current event as a
+dictionary, and modify the returned dictionary accordingly.</p>
+<p>If <code>check_event_allowed</code> raises an exception, the module is assumed to have failed.
+The event will not be accepted but is not treated as explicitly rejected, either.
+An HTTP request causing the module check will likely result in a 500 Internal
+Server Error.</p>
+<p>When the boolean returned by the module is <code>False</code>, the event is rejected.
+(Module developers should not use exceptions for rejection.)</p>
+<p>Note that replacing the event only works for events sent by local users, not for events
+received over federation.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="on_create_room"><a class="header" href="#on_create_room"><code>on_create_room</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def on_create_room(
+    requester: &quot;synapse.types.Requester&quot;,
+    request_content: dict,
+    is_requester_admin: bool,
+) -&gt; None
+</code></pre>
+<p>Called when processing a room creation request, with the <code>Requester</code> object for the user
+performing the request, a dictionary representing the room creation request's JSON body
+(see <a href="https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-createroom">the spec</a>
+for a list of possible parameters), and a boolean indicating whether the user performing
+the request is a server admin.</p>
+<p>Modules can modify the <code>request_content</code> (by e.g. adding events to its <code>initial_state</code>),
+or deny the room's creation by raising a <code>module_api.errors.SynapseError</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns without raising an exception, Synapse falls through to the next one. The
+room creation will be forbidden as soon as one of the callbacks raises an exception. If
+this happens, Synapse will not call any of the subsequent implementations of this
+callback.</p>
+<h3 id="check_threepid_can_be_invited"><a class="header" href="#check_threepid_can_be_invited"><code>check_threepid_can_be_invited</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def check_threepid_can_be_invited(
+    medium: str,
+    address: str,
+    state_events: &quot;synapse.types.StateMap&quot;,
+) -&gt; bool:
+</code></pre>
+<p>Called when processing an invite via a third-party identifier (i.e. email or phone number).
+The module must return a boolean indicating whether the invite can go through.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="check_visibility_can_be_modified"><a class="header" href="#check_visibility_can_be_modified"><code>check_visibility_can_be_modified</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def check_visibility_can_be_modified(
+    room_id: str,
+    state_events: &quot;synapse.types.StateMap&quot;,
+    new_visibility: str,
+) -&gt; bool:
+</code></pre>
+<p>Called when changing the visibility of a room in the local public room directory. The
+visibility is a string that's either &quot;public&quot; or &quot;private&quot;. The module must return a
+boolean indicating whether the change can go through.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="on_new_event"><a class="header" href="#on_new_event"><code>on_new_event</code></a></h3>
+<p><em>First introduced in Synapse v1.47.0</em></p>
+<pre><code class="language-python">async def on_new_event(
+    event: &quot;synapse.events.EventBase&quot;,
+    state_events: &quot;synapse.types.StateMap&quot;,
+) -&gt; None:
+</code></pre>
+<p>Called after sending an event into a room. The module is passed the event, as well
+as the state of the room <em>after</em> the event. This means that if the event is a state event,
+it will be included in this state.</p>
+<p>Note that this callback is called when the event has already been processed and stored
+into the room, which means this callback cannot be used to deny persisting the event. To
+deny an incoming event, see <a href="modules/spam_checker_callbacks.html#check_event_for_spam"><code>check_event_for_spam</code></a> instead.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
+<h3 id="check_can_shutdown_room"><a class="header" href="#check_can_shutdown_room"><code>check_can_shutdown_room</code></a></h3>
+<p><em>First introduced in Synapse v1.55.0</em></p>
+<pre><code class="language-python">async def check_can_shutdown_room(
+    user_id: str, room_id: str,
+) -&gt; bool:
+</code></pre>
+<p>Called when an admin user requests the shutdown of a room. The module must return a
+boolean indicating whether the shutdown can go through. If the callback returns <code>False</code>,
+the shutdown will not proceed and the caller will see a <code>M_FORBIDDEN</code> error.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="check_can_deactivate_user"><a class="header" href="#check_can_deactivate_user"><code>check_can_deactivate_user</code></a></h3>
+<p><em>First introduced in Synapse v1.55.0</em></p>
+<pre><code class="language-python">async def check_can_deactivate_user(
+    user_id: str, by_admin: bool,
+) -&gt; bool:
+</code></pre>
+<p>Called when the deactivation of a user is requested. User deactivation can be
+performed by an admin or the user themselves, so developers are encouraged to check the
+requester when implementing this callback. The module must return a
+boolean indicating whether the deactivation can go through. If the callback returns <code>False</code>,
+the deactivation will not proceed and the caller will see a <code>M_FORBIDDEN</code> error.</p>
+<p>The module is passed two parameters, <code>user_id</code> which is the ID of the user being deactivated, and <code>by_admin</code> which is <code>True</code> if the request is made by a serve admin, and <code>False</code> otherwise.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="on_profile_update"><a class="header" href="#on_profile_update"><code>on_profile_update</code></a></h3>
+<p><em>First introduced in Synapse v1.54.0</em></p>
+<pre><code class="language-python">async def on_profile_update(
+    user_id: str,
+    new_profile: &quot;synapse.module_api.ProfileInfo&quot;,
+    by_admin: bool,
+    deactivation: bool,
+) -&gt; None:
+</code></pre>
+<p>Called after updating a local user's profile. The update can be triggered either by the
+user themselves or a server admin. The update can also be triggered by a user being
+deactivated (in which case their display name is set to an empty string (<code>&quot;&quot;</code>) and the
+avatar URL is set to <code>None</code>). The module is passed the Matrix ID of the user whose profile
+has been updated, their new profile, as well as a <code>by_admin</code> boolean that is <code>True</code> if the
+update was triggered by a server admin (and <code>False</code> otherwise), and a <code>deactivated</code>
+boolean that is <code>True</code> if the update is a result of the user being deactivated.</p>
+<p>Note that the <code>by_admin</code> boolean is also <code>True</code> if the profile change happens as a result
+of the user logging in through Single Sign-On, or if a server admin updates their own
+profile.</p>
+<p>Per-room profile changes do not trigger this callback to be called. Synapse administrators
+wishing this callback to be called on every profile change are encouraged to disable
+per-room profiles globally using the <code>allow_per_room_profiles</code> configuration setting in
+Synapse's configuration file.
+This callback is not called when registering a user, even when setting it through the
+<a href="https://matrix-org.github.io/synapse/latest/modules/password_auth_provider_callbacks.html#get_displayname_for_registration"><code>get_displayname_for_registration</code></a>
+module callback.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
+<h3 id="on_user_deactivation_status_changed"><a class="header" href="#on_user_deactivation_status_changed"><code>on_user_deactivation_status_changed</code></a></h3>
+<p><em>First introduced in Synapse v1.54.0</em></p>
+<pre><code class="language-python">async def on_user_deactivation_status_changed(
+    user_id: str, deactivated: bool, by_admin: bool
+) -&gt; None:
+</code></pre>
+<p>Called after deactivating a local user, or reactivating them through the admin API. The
+deactivation can be triggered either by the user themselves or a server admin. The module
+is passed the Matrix ID of the user whose status is changed, as well as a <code>deactivated</code>
+boolean that is <code>True</code> if the user is being deactivated and <code>False</code> if they're being
+reactivated, and a <code>by_admin</code> boolean that is <code>True</code> if the deactivation was triggered by
+a server admin (and <code>False</code> otherwise). This latter <code>by_admin</code> boolean is always <code>True</code>
+if the user is being reactivated, as this operation can only be performed through the
+admin API.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
+<h2 id="example-1"><a class="header" href="#example-1">Example</a></h2>
+<p>The example below is a module that implements the third-party rules callback
+<code>check_event_allowed</code> to censor incoming messages as dictated by a third-party service.</p>
+<pre><code class="language-python">from typing import Optional, Tuple
+
+from synapse.module_api import ModuleApi
+
+_DEFAULT_CENSOR_ENDPOINT = &quot;https://my-internal-service.local/censor-event&quot;
+
+class EventCensorer:
+    def __init__(self, config: dict, api: ModuleApi):
+        self.api = api
+        self._endpoint = config.get(&quot;endpoint&quot;, _DEFAULT_CENSOR_ENDPOINT)
+
+        self.api.register_third_party_rules_callbacks(
+            check_event_allowed=self.check_event_allowed,
+        )
+
+    async def check_event_allowed(
+        self,
+        event: &quot;synapse.events.EventBase&quot;,
+        state_events: &quot;synapse.types.StateMap&quot;,
+    ) -&gt; Tuple[bool, Optional[dict]]:
+        event_dict = event.get_dict()
+        new_event_content = await self.api.http_client.post_json_get_json(
+            uri=self._endpoint, post_json=event_dict,
+        )
+        event_dict[&quot;content&quot;] = new_event_content
+        return event_dict
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="presence-router-callbacks"><a class="header" href="#presence-router-callbacks">Presence router callbacks</a></h1>
+<p>Presence router callbacks allow module developers to specify additional users (local or remote)
+to receive certain presence updates from local users. Presence router callbacks can be 
+registered using the module API's <code>register_presence_router_callbacks</code> method.</p>
+<h2 id="callbacks-2"><a class="header" href="#callbacks-2">Callbacks</a></h2>
+<p>The available presence router callbacks are:</p>
+<h3 id="get_users_for_states"><a class="header" href="#get_users_for_states"><code>get_users_for_states</code></a></h3>
+<p><em>First introduced in Synapse v1.42.0</em></p>
+<pre><code class="language-python">async def get_users_for_states(
+    state_updates: Iterable[&quot;synapse.api.UserPresenceState&quot;],
+) -&gt; Dict[str, Set[&quot;synapse.api.UserPresenceState&quot;]]
+</code></pre>
+<p><strong>Requires</strong> <code>get_interested_users</code> to also be registered</p>
+<p>Called when processing updates to the presence state of one or more users. This callback can
+be used to instruct the server to forward that presence state to specific users. The module
+must return a dictionary that maps from Matrix user IDs (which can be local or remote) to the
+<code>UserPresenceState</code> changes that they should be forwarded.</p>
+<p>Synapse will then attempt to send the specified presence updates to each user when possible.</p>
+<p>If multiple modules implement this callback, Synapse merges all the dictionaries returned
+by the callbacks. If multiple callbacks return a dictionary containing the same key,
+Synapse concatenates the sets associated with this key from each dictionary. </p>
+<h3 id="get_interested_users"><a class="header" href="#get_interested_users"><code>get_interested_users</code></a></h3>
+<p><em>First introduced in Synapse v1.42.0</em></p>
+<pre><code class="language-python">async def get_interested_users(
+    user_id: str
+) -&gt; Union[Set[str], &quot;synapse.module_api.PRESENCE_ALL_USERS&quot;]
+</code></pre>
+<p><strong>Requires</strong> <code>get_users_for_states</code> to also be registered</p>
+<p>Called when determining which users someone should be able to see the presence state of. This
+callback should return complementary results to <code>get_users_for_state</code> or the presence information 
+may not be properly forwarded.</p>
+<p>The callback is given the Matrix user ID for a local user that is requesting presence data and
+should return the Matrix user IDs of the users whose presence state they are allowed to
+query. The returned users can be local or remote. </p>
+<p>Alternatively the callback can return <code>synapse.module_api.PRESENCE_ALL_USERS</code>
+to indicate that the user should receive updates from all known users.</p>
+<p>If multiple modules implement this callback, they will be considered in order. Synapse
+calls each callback one by one, and use a concatenation of all the <code>set</code>s returned by the
+callbacks. If one callback returns <code>synapse.module_api.PRESENCE_ALL_USERS</code>, Synapse uses
+this value instead. If this happens, Synapse does not call any of the subsequent
+implementations of this callback.</p>
+<h2 id="example-2"><a class="header" href="#example-2">Example</a></h2>
+<p>The example below is a module that implements both presence router callbacks, and ensures
+that <code>@alice:example.org</code> receives all presence updates from <code>@bob:example.com</code> and
+<code>@charlie:somewhere.org</code>, regardless of whether Alice shares a room with any of them.</p>
+<pre><code class="language-python">from typing import Dict, Iterable, Set, Union
+
+from synapse.module_api import ModuleApi
+
+
+class CustomPresenceRouter:
+    def __init__(self, config: dict, api: ModuleApi):
+        self.api = api
+
+        self.api.register_presence_router_callbacks(
+            get_users_for_states=self.get_users_for_states,
+            get_interested_users=self.get_interested_users,
+        )
+
+    async def get_users_for_states(
+        self,
+        state_updates: Iterable[&quot;synapse.api.UserPresenceState&quot;],
+    ) -&gt; Dict[str, Set[&quot;synapse.api.UserPresenceState&quot;]]:
+        res = {}
+        for update in state_updates:
+            if (
+                update.user_id == &quot;@bob:example.com&quot;
+                or update.user_id == &quot;@charlie:somewhere.org&quot;
+            ):
+                res.setdefault(&quot;@alice:example.com&quot;, set()).add(update)
+
+        return res
+
+    async def get_interested_users(
+        self,
+        user_id: str,
+    ) -&gt; Union[Set[str], &quot;synapse.module_api.PRESENCE_ALL_USERS&quot;]:
+        if user_id == &quot;@alice:example.com&quot;:
+            return {&quot;@bob:example.com&quot;, &quot;@charlie:somewhere.org&quot;}
+
+        return set()
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="account-validity-callbacks"><a class="header" href="#account-validity-callbacks">Account validity callbacks</a></h1>
+<p>Account validity callbacks allow module developers to add extra steps to verify the
+validity on an account, i.e. see if a user can be granted access to their account on the
+Synapse instance. Account validity callbacks can be registered using the module API's
+<code>register_account_validity_callbacks</code> method.</p>
+<p>The available account validity callbacks are:</p>
+<h3 id="is_user_expired"><a class="header" href="#is_user_expired"><code>is_user_expired</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def is_user_expired(user: str) -&gt; Optional[bool]
+</code></pre>
+<p>Called when processing any authenticated request (except for logout requests). The module
+can return a <code>bool</code> to indicate whether the user has expired and should be locked out of
+their account, or <code>None</code> if the module wasn't able to figure it out. The user is
+represented by their Matrix user ID (e.g. <code>@alice:example.com</code>).</p>
+<p>If the module returns <code>True</code>, the current request will be denied with the error code
+<code>ORG_MATRIX_EXPIRED_ACCOUNT</code> and the HTTP status code 403. Note that this doesn't
+invalidate the user's access token.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h3 id="on_user_registration"><a class="header" href="#on_user_registration"><code>on_user_registration</code></a></h3>
+<p><em>First introduced in Synapse v1.39.0</em></p>
+<pre><code class="language-python">async def on_user_registration(user: str) -&gt; None
+</code></pre>
+<p>Called after successfully registering a user, in case the module needs to perform extra
+operations to keep track of them. (e.g. add them to a database table). The user is
+represented by their Matrix user ID.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="password-auth-provider-callbacks"><a class="header" href="#password-auth-provider-callbacks">Password auth provider callbacks</a></h1>
+<p>Password auth providers offer a way for server administrators to integrate
+their Synapse installation with an external authentication system. The callbacks can be
+registered by using the Module API's <code>register_password_auth_provider_callbacks</code> method.</p>
+<h2 id="callbacks-3"><a class="header" href="#callbacks-3">Callbacks</a></h2>
+<h3 id="auth_checkers"><a class="header" href="#auth_checkers"><code>auth_checkers</code></a></h3>
+<p><em>First introduced in Synapse v1.46.0</em></p>
+<pre><code class="language-python">auth_checkers: Dict[Tuple[str, Tuple[str, ...]], Callable]
+</code></pre>
+<p>A dict mapping from tuples of a login type identifier (such as <code>m.login.password</code>) and a
+tuple of field names (such as <code>(&quot;password&quot;, &quot;secret_thing&quot;)</code>) to authentication checking
+callbacks, which should be of the following form:</p>
+<pre><code class="language-python">async def check_auth(
+    user: str,
+    login_type: str,
+    login_dict: &quot;synapse.module_api.JsonDict&quot;,
+) -&gt; Optional[
+    Tuple[
+        str, 
+        Optional[Callable[[&quot;synapse.module_api.LoginResponse&quot;], Awaitable[None]]]
+    ]
+]
+</code></pre>
+<p>The login type and field names should be provided by the user in the
+request to the <code>/login</code> API. <a href="https://matrix.org/docs/spec/client_server/latest#authentication-types">The Matrix specification</a>
+defines some types, however user defined ones are also allowed.</p>
+<p>The callback is passed the <code>user</code> field provided by the client (which might not be in
+<code>@username:server</code> form), the login type, and a dictionary of login secrets passed by
+the client.</p>
+<p>If the authentication is successful, the module must return the user's Matrix ID (e.g. 
+<code>@alice:example.com</code>) and optionally a callback to be called with the response to the
+<code>/login</code> request. If the module doesn't wish to return a callback, it must return <code>None</code>
+instead.</p>
+<p>If the authentication is unsuccessful, the module must return <code>None</code>.</p>
+<p>If multiple modules register an auth checker for the same login type but with different
+fields, Synapse will refuse to start.</p>
+<p>If multiple modules register an auth checker for the same login type with the same fields,
+then the callbacks will be executed in order, until one returns a Matrix User ID (and
+optionally a callback). In that case, the return value of that callback will be accepted
+and subsequent callbacks will not be fired. If every callback returns <code>None</code>, then the
+authentication fails.</p>
+<h3 id="check_3pid_auth"><a class="header" href="#check_3pid_auth"><code>check_3pid_auth</code></a></h3>
+<p><em>First introduced in Synapse v1.46.0</em></p>
+<pre><code class="language-python">async def check_3pid_auth(
+    medium: str, 
+    address: str,
+    password: str,
+) -&gt; Optional[
+    Tuple[
+        str, 
+        Optional[Callable[[&quot;synapse.module_api.LoginResponse&quot;], Awaitable[None]]]
+    ]
+]
+</code></pre>
+<p>Called when a user attempts to register or log in with a third party identifier,
+such as email. It is passed the medium (eg. <code>email</code>), an address (eg. <code>jdoe@example.com</code>)
+and the user's password.</p>
+<p>If the authentication is successful, the module must return the user's Matrix ID (e.g. 
+<code>@alice:example.com</code>) and optionally a callback to be called with the response to the <code>/login</code> request.
+If the module doesn't wish to return a callback, it must return None instead.</p>
+<p>If the authentication is unsuccessful, the module must return <code>None</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback. If every callback returns <code>None</code>,
+the authentication is denied.</p>
+<h3 id="on_logged_out"><a class="header" href="#on_logged_out"><code>on_logged_out</code></a></h3>
+<p><em>First introduced in Synapse v1.46.0</em></p>
+<pre><code class="language-python">async def on_logged_out(
+    user_id: str,
+    device_id: Optional[str],
+    access_token: str
+) -&gt; None
+</code></pre>
+<p>Called during a logout request for a user. It is passed the qualified user ID, the ID of the
+deactivated device (if any: access tokens are occasionally created without an associated
+device ID), and the (now deactivated) access token.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
+<h3 id="get_username_for_registration"><a class="header" href="#get_username_for_registration"><code>get_username_for_registration</code></a></h3>
+<p><em>First introduced in Synapse v1.52.0</em></p>
+<pre><code class="language-python">async def get_username_for_registration(
+    uia_results: Dict[str, Any],
+    params: Dict[str, Any],
+) -&gt; Optional[str]
+</code></pre>
+<p>Called when registering a new user. The module can return a username to set for the user
+being registered by returning it as a string, or <code>None</code> if it doesn't wish to force a
+username for this user. If a username is returned, it will be used as the local part of a
+user's full Matrix ID (e.g. it's <code>alice</code> in <code>@alice:example.com</code>).</p>
+<p>This callback is called once <a href="https://spec.matrix.org/latest/client-server-api/#user-interactive-authentication-api">User-Interactive Authentication</a>
+has been completed by the user. It is not called when registering a user via SSO. It is
+passed two dictionaries, which include the information that the user has provided during
+the registration process.</p>
+<p>The first dictionary contains the results of the <a href="https://spec.matrix.org/latest/client-server-api/#user-interactive-authentication-api">User-Interactive Authentication</a>
+flow followed by the user. Its keys are the identifiers of every step involved in the flow,
+associated with either a boolean value indicating whether the step was correctly completed,
+or additional information (e.g. email address, phone number...). A list of most existing
+identifiers can be found in the <a href="https://spec.matrix.org/v1.1/client-server-api/#authentication-types">Matrix specification</a>.
+Here's an example featuring all currently supported keys:</p>
+<pre><code class="language-python">{
+    &quot;m.login.dummy&quot;: True,  # Dummy authentication
+    &quot;m.login.terms&quot;: True,  # User has accepted the terms of service for the homeserver
+    &quot;m.login.recaptcha&quot;: True,  # User has completed the recaptcha challenge
+    &quot;m.login.email.identity&quot;: {  # User has provided and verified an email address
+        &quot;medium&quot;: &quot;email&quot;,
+        &quot;address&quot;: &quot;alice@example.com&quot;,
+        &quot;validated_at&quot;: 1642701357084,
+    },
+    &quot;m.login.msisdn&quot;: {  # User has provided and verified a phone number
+        &quot;medium&quot;: &quot;msisdn&quot;,
+        &quot;address&quot;: &quot;33123456789&quot;,
+        &quot;validated_at&quot;: 1642701357084,
+    },
+    &quot;m.login.registration_token&quot;: &quot;sometoken&quot;,  # User has registered through a registration token
+}
+</code></pre>
+<p>The second dictionary contains the parameters provided by the user's client in the request
+to <code>/_matrix/client/v3/register</code>. See the <a href="https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3register">Matrix specification</a>
+for a complete list of these parameters.</p>
+<p>If the module cannot, or does not wish to, generate a username for this user, it must
+return <code>None</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback. If every callback returns <code>None</code>,
+the username provided by the user is used, if any (otherwise one is automatically
+generated).</p>
+<h3 id="get_displayname_for_registration"><a class="header" href="#get_displayname_for_registration"><code>get_displayname_for_registration</code></a></h3>
+<p><em>First introduced in Synapse v1.54.0</em></p>
+<pre><code class="language-python">async def get_displayname_for_registration(
+    uia_results: Dict[str, Any],
+    params: Dict[str, Any],
+) -&gt; Optional[str]
+</code></pre>
+<p>Called when registering a new user. The module can return a display name to set for the
+user being registered by returning it as a string, or <code>None</code> if it doesn't wish to force a
+display name for this user.</p>
+<p>This callback is called once <a href="https://spec.matrix.org/latest/client-server-api/#user-interactive-authentication-api">User-Interactive Authentication</a>
+has been completed by the user. It is not called when registering a user via SSO. It is
+passed two dictionaries, which include the information that the user has provided during
+the registration process. These dictionaries are identical to the ones passed to
+<a href="modules/password_auth_provider_callbacks.html#get_username_for_registration"><code>get_username_for_registration</code></a>, so refer to the
+documentation of this callback for more information about them.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback. If every callback returns <code>None</code>,
+the username will be used (e.g. <code>alice</code> if the user being registered is <code>@alice:example.com</code>).</p>
+<h2 id="is_3pid_allowed"><a class="header" href="#is_3pid_allowed"><code>is_3pid_allowed</code></a></h2>
+<p><em>First introduced in Synapse v1.53.0</em></p>
+<pre><code class="language-python">async def is_3pid_allowed(self, medium: str, address: str, registration: bool) -&gt; bool
+</code></pre>
+<p>Called when attempting to bind a third-party identifier (i.e. an email address or a phone
+number). The module is given the medium of the third-party identifier (which is <code>email</code> if
+the identifier is an email address, or <code>msisdn</code> if the identifier is a phone number) and
+its address, as well as a boolean indicating whether the attempt to bind is happening as
+part of registering a new user. The module must return a boolean indicating whether the
+identifier can be allowed to be bound to an account on the local homeserver.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
+<h2 id="example-3"><a class="header" href="#example-3">Example</a></h2>
+<p>The example module below implements authentication checkers for two different login types: </p>
+<ul>
+<li><code>my.login.type</code> 
+<ul>
+<li>Expects a <code>my_field</code> field to be sent to <code>/login</code></li>
+<li>Is checked by the method: <code>self.check_my_login</code></li>
+</ul>
+</li>
+<li><code>m.login.password</code> (defined in <a href="https://matrix.org/docs/spec/client_server/latest#password-based">the spec</a>)
+<ul>
+<li>Expects a <code>password</code> field to be sent to <code>/login</code></li>
+<li>Is checked by the method: <code>self.check_pass</code></li>
+</ul>
+</li>
+</ul>
+<pre><code class="language-python">from typing import Awaitable, Callable, Optional, Tuple
+
+import synapse
+from synapse import module_api
+
+
+class MyAuthProvider:
+    def __init__(self, config: dict, api: module_api):
+
+        self.api = api
+
+        self.credentials = {
+            &quot;bob&quot;: &quot;building&quot;,
+            &quot;@scoop:matrix.org&quot;: &quot;digging&quot;,
+        }
+
+        api.register_password_auth_provider_callbacks(
+            auth_checkers={
+                (&quot;my.login_type&quot;, (&quot;my_field&quot;,)): self.check_my_login,
+                (&quot;m.login.password&quot;, (&quot;password&quot;,)): self.check_pass,
+            },
+        )
+
+    async def check_my_login(
+        self,
+        username: str,
+        login_type: str,
+        login_dict: &quot;synapse.module_api.JsonDict&quot;,
+    ) -&gt; Optional[
+        Tuple[
+            str,
+            Optional[Callable[[&quot;synapse.module_api.LoginResponse&quot;], Awaitable[None]]],
+        ]
+    ]:
+        if login_type != &quot;my.login_type&quot;:
+            return None
+
+        if self.credentials.get(username) == login_dict.get(&quot;my_field&quot;):
+            return self.api.get_qualified_user_id(username)
+
+    async def check_pass(
+        self,
+        username: str,
+        login_type: str,
+        login_dict: &quot;synapse.module_api.JsonDict&quot;,
+    ) -&gt; Optional[
+        Tuple[
+            str,
+            Optional[Callable[[&quot;synapse.module_api.LoginResponse&quot;], Awaitable[None]]],
+        ]
+    ]:
+        if login_type != &quot;m.login.password&quot;:
+            return None
+
+        if self.credentials.get(username) == login_dict.get(&quot;password&quot;):
+            return self.api.get_qualified_user_id(username)
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="background-update-controller-callbacks"><a class="header" href="#background-update-controller-callbacks">Background update controller callbacks</a></h1>
+<p>Background update controller callbacks allow module developers to control (e.g. rate-limit)
+how database background updates are run. A database background update is an operation
+Synapse runs on its database in the background after it starts. It's usually used to run
+database operations that would take too long if they were run at the same time as schema
+updates (which are run on startup) and delay Synapse's startup too much: populating a
+table with a big amount of data, adding an index on a big table, deleting superfluous data,
+etc.</p>
+<p>Background update controller callbacks can be registered using the module API's
+<code>register_background_update_controller_callbacks</code> method. Only the first module (in order
+of appearance in Synapse's configuration file) calling this method can register background
+update controller callbacks, subsequent calls are ignored.</p>
+<p>The available background update controller callbacks are:</p>
+<h3 id="on_update"><a class="header" href="#on_update"><code>on_update</code></a></h3>
+<p><em>First introduced in Synapse v1.49.0</em></p>
+<pre><code class="language-python">def on_update(update_name: str, database_name: str, one_shot: bool) -&gt; AsyncContextManager[int]
+</code></pre>
+<p>Called when about to do an iteration of a background update. The module is given the name
+of the update, the name of the database, and a flag to indicate whether the background
+update will happen in one go and may take a long time (e.g. creating indices). If this last
+argument is set to <code>False</code>, the update will be run in batches.</p>
+<p>The module must return an async context manager. It will be entered before Synapse runs a 
+background update; this should return the desired duration of the iteration, in
+milliseconds.</p>
+<p>The context manager will be exited when the iteration completes. Note that the duration
+returned by the context manager is a target, and an iteration may take substantially longer
+or shorter. If the <code>one_shot</code> flag is set to <code>True</code>, the duration returned is ignored.</p>
+<p><strong>Note</strong>: Unlike most module callbacks in Synapse, this one is <em>synchronous</em>. This is
+because asynchronous operations are expected to be run by the async context manager.</p>
+<p>This callback is required when registering any other background update controller callback.</p>
+<h3 id="default_batch_size"><a class="header" href="#default_batch_size"><code>default_batch_size</code></a></h3>
+<p><em>First introduced in Synapse v1.49.0</em></p>
+<pre><code class="language-python">async def default_batch_size(update_name: str, database_name: str) -&gt; int
+</code></pre>
+<p>Called before the first iteration of a background update, with the name of the update and
+of the database. The module must return the number of elements to process in this first
+iteration.</p>
+<p>If this callback is not defined, Synapse will use a default value of 100.</p>
+<h3 id="min_batch_size"><a class="header" href="#min_batch_size"><code>min_batch_size</code></a></h3>
+<p><em>First introduced in Synapse v1.49.0</em></p>
+<pre><code class="language-python">async def min_batch_size(update_name: str, database_name: str) -&gt; int
+</code></pre>
+<p>Called before running a new batch for a background update, with the name of the update and
+of the database. The module must return an integer representing the minimum number of
+elements to process in this iteration. This number must be at least 1, and is used to
+ensure that progress is always made.</p>
+<p>If this callback is not defined, Synapse will use a default value of 100.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="porting-an-existing-module-that-uses-the-old-interface"><a class="header" href="#porting-an-existing-module-that-uses-the-old-interface">Porting an existing module that uses the old interface</a></h1>
+<p>In order to port a module that uses Synapse's old module interface, its author needs to:</p>
+<ul>
+<li>ensure the module's callbacks are all asynchronous.</li>
+<li>register their callbacks using one or more of the <code>register_[...]_callbacks</code> methods
+from the <code>ModuleApi</code> class in the module's <code>__init__</code> method (see <a href="modules/writing_a_module.html#registering-a-callback">this section</a>
+for more info).</li>
+</ul>
+<p>Additionally, if the module is packaged with an additional web resource, the module
+should register this resource in its <code>__init__</code> method using the <code>register_web_resource</code>
+method from the <code>ModuleApi</code> class (see <a href="modules/writing_a_module.html#registering-a-web-resource">this section</a> for
+more info).</p>
+<p>There is no longer a <code>get_db_schema_files</code> callback provided for password auth provider modules. Any
+changes to the database should now be made by the module using the module API class.</p>
+<p>The module's author should also update any example in the module's configuration to only
+use the new <code>modules</code> section in Synapse's configuration file (see <a href="modules/index.html#using-modules">this section</a>
+for more info).</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="scaling-synapse-via-workers"><a class="header" href="#scaling-synapse-via-workers">Scaling synapse via workers</a></h1>
+<p>For small instances it recommended to run Synapse in the default monolith mode.
+For larger instances where performance is a concern it can be helpful to split
+out functionality into multiple separate python processes. These processes are
+called 'workers', and are (eventually) intended to scale horizontally
+independently.</p>
+<p>Synapse's worker support is under active development and subject to change as
+we attempt to rapidly scale ever larger Synapse instances. However we are
+documenting it here to help admins needing a highly scalable Synapse instance
+similar to the one running <code>matrix.org</code>.</p>
+<p>All processes continue to share the same database instance, and as such,
+workers only work with PostgreSQL-based Synapse deployments. SQLite should only
+be used for demo purposes and any admin considering workers should already be
+running PostgreSQL.</p>
+<p>See also <a href="https://matrix.org/blog/2020/11/03/how-we-fixed-synapses-scalability">Matrix.org blog post</a>
+for a higher level overview.</p>
+<h2 id="main-processworker-communication"><a class="header" href="#main-processworker-communication">Main process/worker communication</a></h2>
+<p>The processes communicate with each other via a Synapse-specific protocol called
+'replication' (analogous to MySQL- or Postgres-style database replication) which
+feeds streams of newly written data between processes so they can be kept in
+sync with the database state.</p>
+<p>When configured to do so, Synapse uses a
+<a href="https://redis.io/topics/pubsub">Redis pub/sub channel</a> to send the replication
+stream between all configured Synapse processes. Additionally, processes may
+make HTTP requests to each other, primarily for operations which need to wait
+for a reply ─ such as sending an event.</p>
+<p>Redis support was added in v1.13.0 with it becoming the recommended method in
+v1.18.0. It replaced the old direct TCP connections (which is deprecated as of
+v1.18.0) to the main process. With Redis, rather than all the workers connecting
+to the main process, all the workers and the main process connect to Redis,
+which relays replication commands between processes. This can give a significant
+cpu saving on the main process and will be a prerequisite for upcoming
+performance improvements.</p>
+<p>If Redis support is enabled Synapse will use it as a shared cache, as well as a
+pub/sub mechanism.</p>
+<p>See the <a href="workers.html#architectural-diagram">Architectural diagram</a> section at the end for
+a visualisation of what this looks like.</p>
+<h2 id="setting-up-workers"><a class="header" href="#setting-up-workers">Setting up workers</a></h2>
+<p>A Redis server is required to manage the communication between the processes.
+The Redis server should be installed following the normal procedure for your
+distribution (e.g. <code>apt install redis-server</code> on Debian). It is safe to use an
+existing Redis deployment if you have one.</p>
+<p>Once installed, check that Redis is running and accessible from the host running
+Synapse, for example by executing <code>echo PING | nc -q1 localhost 6379</code> and seeing
+a response of <code>+PONG</code>.</p>
+<p>The appropriate dependencies must also be installed for Synapse. If using a
+virtualenv, these can be installed with:</p>
+<pre><code class="language-sh">pip install &quot;matrix-synapse[redis]&quot;
+</code></pre>
+<p>Note that these dependencies are included when synapse is installed with <code>pip install matrix-synapse[all]</code>. They are also included in the debian packages from
+<code>matrix.org</code> and in the docker images at
+https://hub.docker.com/r/matrixdotorg/synapse/.</p>
+<p>To make effective use of the workers, you will need to configure an HTTP
+reverse-proxy such as nginx or haproxy, which will direct incoming requests to
+the correct worker, or to the main synapse instance. See
+<a href="reverse_proxy.html">the reverse proxy documentation</a> for information on setting up a reverse
+proxy.</p>
+<p>When using workers, each worker process has its own configuration file which
+contains settings specific to that worker, such as the HTTP listener that it
+provides (if any), logging configuration, etc.</p>
+<p>Normally, the worker processes are configured to read from a shared
+configuration file as well as the worker-specific configuration files. This
+makes it easier to keep common configuration settings synchronised across all
+the processes.</p>
+<p>The main process is somewhat special in this respect: it does not normally
+need its own configuration file and can take all of its configuration from the
+shared configuration file.</p>
+<h3 id="shared-configuration"><a class="header" href="#shared-configuration">Shared configuration</a></h3>
+<p>Normally, only a couple of changes are needed to make an existing configuration
+file suitable for use with workers. First, you need to enable an &quot;HTTP replication
+listener&quot; for the main process; and secondly, you need to enable redis-based
+replication. Optionally, a shared secret can be used to authenticate HTTP
+traffic between workers. For example:</p>
+<pre><code class="language-yaml"># extend the existing `listeners` section. This defines the ports that the
+# main process will listen on.
+listeners:
+  # The HTTP replication port
+  - port: 9093
+    bind_address: '127.0.0.1'
+    type: http
+    resources:
+     - names: [replication]
+
+# Add a random shared secret to authenticate traffic.
+worker_replication_secret: &quot;&quot;
+
+redis:
+    enabled: true
+</code></pre>
+<p>See the sample config for the full documentation of each option.</p>
+<p>Under <strong>no circumstances</strong> should the replication listener be exposed to the
+public internet; it has no authentication and is unencrypted.</p>
+<h3 id="worker-configuration"><a class="header" href="#worker-configuration">Worker configuration</a></h3>
+<p>In the config file for each worker, you must specify the type of worker
+application (<code>worker_app</code>), and you should specify a unique name for the worker
+(<code>worker_name</code>). The currently available worker applications are listed below.
+You must also specify the HTTP replication endpoint that it should talk to on
+the main synapse process.  <code>worker_replication_host</code> should specify the host of
+the main synapse and <code>worker_replication_http_port</code> should point to the HTTP
+replication port. If the worker will handle HTTP requests then the
+<code>worker_listeners</code> option should be set with a <code>http</code> listener, in the same way
+as the <code>listeners</code> option in the shared config.</p>
+<p>For example:</p>
+<pre><code class="language-yaml">worker_app: synapse.app.generic_worker
+worker_name: worker1
+
+# The replication listener on the main synapse process.
+worker_replication_host: 127.0.0.1
+worker_replication_http_port: 9093
+
+worker_listeners:
+ - type: http
+   port: 8083
+   resources:
+     - names:
+       - client
+       - federation
+
+worker_log_config: /home/matrix/synapse/config/worker1_log_config.yaml
+</code></pre>
+<p>...is a full configuration for a generic worker instance, which will expose a
+plain HTTP endpoint on port 8083 separately serving various endpoints, e.g.
+<code>/sync</code>, which are listed below.</p>
+<p>Obviously you should configure your reverse-proxy to route the relevant
+endpoints to the worker (<code>localhost:8083</code> in the above example).</p>
+<h3 id="running-synapse-with-workers"><a class="header" href="#running-synapse-with-workers">Running Synapse with workers</a></h3>
+<p>Finally, you need to start your worker processes. This can be done with either
+<code>synctl</code> or your distribution's preferred service manager such as <code>systemd</code>. We
+recommend the use of <code>systemd</code> where available: for information on setting up
+<code>systemd</code> to start synapse workers, see
+<a href="systemd-with-workers">Systemd with Workers</a>. To use <code>synctl</code>, see
+<a href="synctl_workers.html">Using synctl with Workers</a>.</p>
+<h2 id="available-worker-applications"><a class="header" href="#available-worker-applications">Available worker applications</a></h2>
+<h3 id="synapseappgeneric_worker"><a class="header" href="#synapseappgeneric_worker"><code>synapse.app.generic_worker</code></a></h3>
+<p>This worker can handle API requests matching the following regular expressions.
+These endpoints can be routed to any worker. If a worker is set up to handle a
+stream then, for maximum efficiency, additional endpoints should be routed to that
+worker: refer to the <a href="workers.html#stream-writers">stream writers</a> section below for further
+information.</p>
+<pre><code># Sync requests
+^/_matrix/client/(r0|v3)/sync$
+^/_matrix/client/(api/v1|r0|v3)/events$
+^/_matrix/client/(api/v1|r0|v3)/initialSync$
+^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$
+
+# Federation requests
+^/_matrix/federation/v1/event/
+^/_matrix/federation/v1/state/
+^/_matrix/federation/v1/state_ids/
+^/_matrix/federation/v1/backfill/
+^/_matrix/federation/v1/get_missing_events/
+^/_matrix/federation/v1/publicRooms
+^/_matrix/federation/v1/query/
+^/_matrix/federation/v1/make_join/
+^/_matrix/federation/v1/make_leave/
+^/_matrix/federation/(v1|v2)/send_join/
+^/_matrix/federation/(v1|v2)/send_leave/
+^/_matrix/federation/(v1|v2)/invite/
+^/_matrix/federation/v1/event_auth/
+^/_matrix/federation/v1/exchange_third_party_invite/
+^/_matrix/federation/v1/user/devices/
+^/_matrix/federation/v1/get_groups_publicised$
+^/_matrix/key/v2/query
+^/_matrix/federation/(v1|unstable/org.matrix.msc2946)/hierarchy/
+
+# Inbound federation transaction request
+^/_matrix/federation/v1/send/
+
+# Client API requests
+^/_matrix/client/(api/v1|r0|v3|unstable)/createRoom$
+^/_matrix/client/(api/v1|r0|v3|unstable)/publicRooms$
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/joined_members$
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/context/.*$
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/members$
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/state$
+^/_matrix/client/(v1|unstable/org.matrix.msc2946)/rooms/.*/hierarchy$
+^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary$
+^/_matrix/client/(r0|v3|unstable)/account/3pid$
+^/_matrix/client/(r0|v3|unstable)/devices$
+^/_matrix/client/versions$
+^/_matrix/client/(api/v1|r0|v3|unstable)/voip/turnServer$
+^/_matrix/client/(r0|v3|unstable)/joined_groups$
+^/_matrix/client/(r0|v3|unstable)/publicised_groups$
+^/_matrix/client/(r0|v3|unstable)/publicised_groups/
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/event/
+^/_matrix/client/(api/v1|r0|v3|unstable)/joined_rooms$
+^/_matrix/client/(api/v1|r0|v3|unstable)/search$
+
+# Encryption requests
+^/_matrix/client/(r0|v3|unstable)/keys/query$
+^/_matrix/client/(r0|v3|unstable)/keys/changes$
+^/_matrix/client/(r0|v3|unstable)/keys/claim$
+^/_matrix/client/(r0|v3|unstable)/room_keys/
+
+# Registration/login requests
+^/_matrix/client/(api/v1|r0|v3|unstable)/login$
+^/_matrix/client/(r0|v3|unstable)/register$
+^/_matrix/client/v1/register/m.login.registration_token/validity$
+
+# Event sending requests
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/redact
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/send
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/state/
+^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/(join|invite|leave|ban|unban|kick)$
+^/_matrix/client/(api/v1|r0|v3|unstable)/join/
+^/_matrix/client/(api/v1|r0|v3|unstable)/profile/
+
+# Device requests
+^/_matrix/client/(r0|v3|unstable)/sendToDevice/
+
+# Account data requests
+^/_matrix/client/(r0|v3|unstable)/.*/tags
+^/_matrix/client/(r0|v3|unstable)/.*/account_data
+
+# Receipts requests
+^/_matrix/client/(r0|v3|unstable)/rooms/.*/receipt
+^/_matrix/client/(r0|v3|unstable)/rooms/.*/read_markers
+
+# Presence requests
+^/_matrix/client/(api/v1|r0|v3|unstable)/presence/
+</code></pre>
+<p>Additionally, the following REST endpoints can be handled for GET requests:</p>
+<pre><code>^/_matrix/federation/v1/groups/
+^/_matrix/client/(api/v1|r0|v3|unstable)/pushrules/
+^/_matrix/client/(r0|v3|unstable)/groups/
+</code></pre>
+<p>Pagination requests can also be handled, but all requests for a given
+room must be routed to the same instance. Additionally, care must be taken to
+ensure that the purge history admin API is not used while pagination requests
+for the room are in flight:</p>
+<pre><code>^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/messages$
+</code></pre>
+<p>Additionally, the following endpoints should be included if Synapse is configured
+to use SSO (you only need to include the ones for whichever SSO provider you're
+using):</p>
+<pre><code># for all SSO providers
+^/_matrix/client/(api/v1|r0|v3|unstable)/login/sso/redirect
+^/_synapse/client/pick_idp$
+^/_synapse/client/pick_username
+^/_synapse/client/new_user_consent$
+^/_synapse/client/sso_register$
+
+# OpenID Connect requests.
+^/_synapse/client/oidc/callback$
+
+# SAML requests.
+^/_synapse/client/saml2/authn_response$
+
+# CAS requests.
+^/_matrix/client/(api/v1|r0|v3|unstable)/login/cas/ticket$
+</code></pre>
+<p>Ensure that all SSO logins go to a single process.
+For multiple workers not handling the SSO endpoints properly, see
+<a href="https://github.com/matrix-org/synapse/issues/7530">#7530</a> and
+<a href="https://github.com/matrix-org/synapse/issues/9427">#9427</a>.</p>
+<p>Note that a HTTP listener with <code>client</code> and <code>federation</code> resources must be
+configured in the <code>worker_listeners</code> option in the worker config.</p>
+<h4 id="load-balancing"><a class="header" href="#load-balancing">Load balancing</a></h4>
+<p>It is possible to run multiple instances of this worker app, with incoming requests
+being load-balanced between them by the reverse-proxy. However, different endpoints
+have different characteristics and so admins
+may wish to run multiple groups of workers handling different endpoints so that
+load balancing can be done in different ways.</p>
+<p>For <code>/sync</code> and <code>/initialSync</code> requests it will be more efficient if all
+requests from a particular user are routed to a single instance. Extracting a
+user ID from the access token or <code>Authorization</code> header is currently left as an
+exercise for the reader. Admins may additionally wish to separate out <code>/sync</code>
+requests that have a <code>since</code> query parameter from those that don't (and
+<code>/initialSync</code>), as requests that don't are known as &quot;initial sync&quot; that happens
+when a user logs in on a new device and can be <em>very</em> resource intensive, so
+isolating these requests will stop them from interfering with other users ongoing
+syncs.</p>
+<p>Federation and client requests can be balanced via simple round robin.</p>
+<p>The inbound federation transaction request <code>^/_matrix/federation/v1/send/</code>
+should be balanced by source IP so that transactions from the same remote server
+go to the same process.</p>
+<p>Registration/login requests can be handled separately purely to help ensure that
+unexpected load doesn't affect new logins and sign ups.</p>
+<p>Finally, event sending requests can be balanced by the room ID in the URI (or
+the full URI, or even just round robin), the room ID is the path component after
+<code>/rooms/</code>. If there is a large bridge connected that is sending or may send lots
+of events, then a dedicated set of workers can be provisioned to limit the
+effects of bursts of events from that bridge on events sent by normal users.</p>
+<h4 id="stream-writers"><a class="header" href="#stream-writers">Stream writers</a></h4>
+<p>Additionally, there is <em>experimental</em> support for moving writing of specific
+streams (such as events) off of the main process to a particular worker. (This
+is only supported with Redis-based replication.)</p>
+<p>To enable this, the worker must have a HTTP replication listener configured,
+have a <code>worker_name</code> and be listed in the <code>instance_map</code> config. The same worker
+can handle multiple streams, but unless otherwise documented, each stream can only
+have a single writer.</p>
+<p>For example, to move event persistence off to a dedicated worker, the shared
+configuration would include:</p>
+<pre><code class="language-yaml">instance_map:
+    event_persister1:
+        host: localhost
+        port: 8034
+
+stream_writers:
+    events: event_persister1
+</code></pre>
+<p>Some of the streams have associated endpoints which, for maximum efficiency, should
+be routed to the workers handling that stream. See below for the currently supported
+streams and the endpoints associated with them:</p>
+<h5 id="the-events-stream"><a class="header" href="#the-events-stream">The <code>events</code> stream</a></h5>
+<p>The <code>events</code> stream experimentally supports having multiple writers, where work
+is sharded between them by room ID. Note that you <em>must</em> restart all worker
+instances when adding or removing event persisters. An example <code>stream_writers</code>
+configuration with multiple writers:</p>
+<pre><code class="language-yaml">stream_writers:
+    events:
+        - event_persister1
+        - event_persister2
+</code></pre>
+<h5 id="the-typing-stream"><a class="header" href="#the-typing-stream">The <code>typing</code> stream</a></h5>
+<p>The following endpoints should be routed directly to the worker configured as
+the stream writer for the <code>typing</code> stream:</p>
+<pre><code>^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/typing
+</code></pre>
+<h5 id="the-to_device-stream"><a class="header" href="#the-to_device-stream">The <code>to_device</code> stream</a></h5>
+<p>The following endpoints should be routed directly to the worker configured as
+the stream writer for the <code>to_device</code> stream:</p>
+<pre><code>^/_matrix/client/(r0|v3|unstable)/sendToDevice/
+</code></pre>
+<h5 id="the-account_data-stream"><a class="header" href="#the-account_data-stream">The <code>account_data</code> stream</a></h5>
+<p>The following endpoints should be routed directly to the worker configured as
+the stream writer for the <code>account_data</code> stream:</p>
+<pre><code>^/_matrix/client/(r0|v3|unstable)/.*/tags
+^/_matrix/client/(r0|v3|unstable)/.*/account_data
+</code></pre>
+<h5 id="the-receipts-stream"><a class="header" href="#the-receipts-stream">The <code>receipts</code> stream</a></h5>
+<p>The following endpoints should be routed directly to the worker configured as
+the stream writer for the <code>receipts</code> stream:</p>
+<pre><code>^/_matrix/client/(r0|v3|unstable)/rooms/.*/receipt
+^/_matrix/client/(r0|v3|unstable)/rooms/.*/read_markers
+</code></pre>
+<h5 id="the-presence-stream"><a class="header" href="#the-presence-stream">The <code>presence</code> stream</a></h5>
+<p>The following endpoints should be routed directly to the worker configured as
+the stream writer for the <code>presence</code> stream:</p>
+<pre><code>^/_matrix/client/(api/v1|r0|v3|unstable)/presence/
+</code></pre>
+<h4 id="background-tasks"><a class="header" href="#background-tasks">Background tasks</a></h4>
+<p>There is also <em>experimental</em> support for moving background tasks to a separate
+worker. Background tasks are run periodically or started via replication. Exactly
+which tasks are configured to run depends on your Synapse configuration (e.g. if
+stats is enabled).</p>
+<p>To enable this, the worker must have a <code>worker_name</code> and can be configured to run
+background tasks. For example, to move background tasks to a dedicated worker,
+the shared configuration would include:</p>
+<pre><code class="language-yaml">run_background_tasks_on: background_worker
+</code></pre>
+<p>You might also wish to investigate the <code>update_user_directory</code> and
+<code>media_instance_running_background_jobs</code> settings.</p>
+<h3 id="synapseapppusher"><a class="header" href="#synapseapppusher"><code>synapse.app.pusher</code></a></h3>
+<p>Handles sending push notifications to sygnal and email. Doesn't handle any
+REST endpoints itself, but you should set <code>start_pushers: False</code> in the
+shared configuration file to stop the main synapse sending push notifications.</p>
+<p>To run multiple instances at once the <code>pusher_instances</code> option should list all
+pusher instances by their worker name, e.g.:</p>
+<pre><code class="language-yaml">pusher_instances:
+    - pusher_worker1
+    - pusher_worker2
+</code></pre>
+<h3 id="synapseappappservice"><a class="header" href="#synapseappappservice"><code>synapse.app.appservice</code></a></h3>
+<p>Handles sending output traffic to Application Services. Doesn't handle any
+REST endpoints itself, but you should set <code>notify_appservices: False</code> in the
+shared configuration file to stop the main synapse sending appservice notifications.</p>
+<p>Note this worker cannot be load-balanced: only one instance should be active.</p>
+<h3 id="synapseappfederation_sender"><a class="header" href="#synapseappfederation_sender"><code>synapse.app.federation_sender</code></a></h3>
+<p>Handles sending federation traffic to other servers. Doesn't handle any
+REST endpoints itself, but you should set <code>send_federation: False</code> in the
+shared configuration file to stop the main synapse sending this traffic.</p>
+<p>If running multiple federation senders then you must list each
+instance in the <code>federation_sender_instances</code> option by their <code>worker_name</code>.
+All instances must be stopped and started when adding or removing instances.
+For example:</p>
+<pre><code class="language-yaml">federation_sender_instances:
+    - federation_sender1
+    - federation_sender2
+</code></pre>
+<h3 id="synapseappmedia_repository"><a class="header" href="#synapseappmedia_repository"><code>synapse.app.media_repository</code></a></h3>
+<p>Handles the media repository. It can handle all endpoints starting with:</p>
+<pre><code>/_matrix/media/
+</code></pre>
+<p>... and the following regular expressions matching media-specific administration APIs:</p>
+<pre><code>^/_synapse/admin/v1/purge_media_cache$
+^/_synapse/admin/v1/room/.*/media.*$
+^/_synapse/admin/v1/user/.*/media.*$
+^/_synapse/admin/v1/media/.*$
+^/_synapse/admin/v1/quarantine_media/.*$
+^/_synapse/admin/v1/users/.*/media$
+</code></pre>
+<p>You should also set <code>enable_media_repo: False</code> in the shared configuration
+file to stop the main synapse running background jobs related to managing the
+media repository. Note that doing so will prevent the main process from being
+able to handle the above endpoints.</p>
+<p>In the <code>media_repository</code> worker configuration file, configure the http listener to
+expose the <code>media</code> resource. For example:</p>
+<pre><code class="language-yaml">worker_listeners:
+ - type: http
+   port: 8085
+   resources:
+     - names:
+       - media
+</code></pre>
+<p>Note that if running multiple media repositories they must be on the same server
+and you must configure a single instance to run the background tasks, e.g.:</p>
+<pre><code class="language-yaml">media_instance_running_background_jobs: &quot;media-repository-1&quot;
+</code></pre>
+<p>Note that if a reverse proxy is used , then <code>/_matrix/media/</code> must be routed for both inbound client and federation requests (if they are handled separately).</p>
+<h3 id="synapseappuser_dir"><a class="header" href="#synapseappuser_dir"><code>synapse.app.user_dir</code></a></h3>
+<p>Handles searches in the user directory. It can handle REST endpoints matching
+the following regular expressions:</p>
+<pre><code>^/_matrix/client/(r0|v3|unstable)/user_directory/search$
+</code></pre>
+<p>When using this worker you must also set <code>update_user_directory: false</code> in the
+shared configuration file to stop the main synapse running background
+jobs related to updating the user directory.</p>
+<p>Above endpoint is not <em>required</em> to be routed to this worker. By default,
+<code>update_user_directory</code> is set to <code>true</code>, which means the main process
+will handle updates. All workers configured with <code>client</code> can handle the above
+endpoint as long as either this worker or the main process are configured to
+handle it, and are online.</p>
+<p>If <code>update_user_directory</code> is set to <code>false</code>, and this worker is not running,
+the above endpoint may give outdated results.</p>
+<h3 id="synapseappfrontend_proxy"><a class="header" href="#synapseappfrontend_proxy"><code>synapse.app.frontend_proxy</code></a></h3>
+<p>Proxies some frequently-requested client endpoints to add caching and remove
+load from the main synapse. It can handle REST endpoints matching the following
+regular expressions:</p>
+<pre><code>^/_matrix/client/(r0|v3|unstable)/keys/upload
+</code></pre>
+<p>If <code>use_presence</code> is False in the homeserver config, it can also handle REST
+endpoints matching the following regular expressions:</p>
+<pre><code>^/_matrix/client/(api/v1|r0|v3|unstable)/presence/[^/]+/status
+</code></pre>
+<p>This &quot;stub&quot; presence handler will pass through <code>GET</code> request but make the
+<code>PUT</code> effectively a no-op.</p>
+<p>It will proxy any requests it cannot handle to the main synapse instance. It
+must therefore be configured with the location of the main instance, via
+the <code>worker_main_http_uri</code> setting in the <code>frontend_proxy</code> worker configuration
+file. For example:</p>
+<pre><code class="language-yaml">worker_main_http_uri: http://127.0.0.1:8008
+</code></pre>
+<h3 id="historical-apps"><a class="header" href="#historical-apps">Historical apps</a></h3>
+<p><em>Note:</em> Historically there used to be more apps, however they have been
+amalgamated into a single <code>synapse.app.generic_worker</code> app. The remaining apps
+are ones that do specific processing unrelated to requests, e.g. the <code>pusher</code>
+that handles sending out push notifications for new events. The intention is for
+all these to be folded into the <code>generic_worker</code> app and to use config to define
+which processes handle the various proccessing such as push notifications.</p>
+<h2 id="migration-from-old-config"><a class="header" href="#migration-from-old-config">Migration from old config</a></h2>
+<p>There are two main independent changes that have been made: introducing Redis
+support and merging apps into <code>synapse.app.generic_worker</code>. Both these changes
+are backwards compatible and so no changes to the config are required, however
+server admins are encouraged to plan to migrate to Redis as the old style direct
+TCP replication config is deprecated.</p>
+<p>To migrate to Redis add the <code>redis</code> config as above, and optionally remove the
+TCP <code>replication</code> listener from master and <code>worker_replication_port</code> from worker
+config.</p>
+<p>To migrate apps to use <code>synapse.app.generic_worker</code> simply update the
+<code>worker_app</code> option in the worker configs, and where worker are started (e.g.
+in systemd service files, but not required for synctl).</p>
+<h2 id="architectural-diagram"><a class="header" href="#architectural-diagram">Architectural diagram</a></h2>
+<p>The following shows an example setup using Redis and a reverse proxy:</p>
+<pre><code>                     Clients &amp; Federation
+                              |
+                              v
+                        +-----------+
+                        |           |
+                        |  Reverse  |
+                        |  Proxy    |
+                        |           |
+                        +-----------+
+                            | | |
+                            | | | HTTP requests
+        +-------------------+ | +-----------+
+        |                 +---+             |
+        |                 |                 |
+        v                 v                 v
++--------------+  +--------------+  +--------------+  +--------------+
+|   Main       |  |   Generic    |  |   Generic    |  |  Event       |
+|   Process    |  |   Worker 1   |  |   Worker 2   |  |  Persister   |
++--------------+  +--------------+  +--------------+  +--------------+
+      ^    ^          |   ^   |         |   ^   |          ^    ^
+      |    |          |   |   |         |   |   |          |    |
+      |    |          |   |   |  HTTP   |   |   |          |    |
+      |    +----------+&lt;--|---|---------+   |   |          |    |
+      |                   |   +-------------|--&gt;+----------+    |
+      |                   |                 |                   |
+      |                   |                 |                   |
+      v                   v                 v                   v
+====================================================================
+                                                         Redis pub/sub channel
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h3 id="using-synctl-with-workers"><a class="header" href="#using-synctl-with-workers">Using synctl with workers</a></h3>
+<p>If you want to use <code>synctl</code> to manage your synapse processes, you will need to
+create an an additional configuration file for the main synapse process. That
+configuration should look like this:</p>
+<pre><code class="language-yaml">worker_app: synapse.app.homeserver
+</code></pre>
+<p>Additionally, each worker app must be configured with the name of a &quot;pid file&quot;,
+to which it will write its process ID when it starts. For example, for a
+synchrotron, you might write:</p>
+<pre><code class="language-yaml">worker_pid_file: /home/matrix/synapse/worker1.pid
+</code></pre>
+<p>Finally, to actually run your worker-based synapse, you must pass synctl the <code>-a</code>
+commandline option to tell it to operate on all the worker configurations found
+in the given directory, e.g.:</p>
+<pre><code class="language-sh">synctl -a $CONFIG/workers start
+</code></pre>
+<p>Currently one should always restart all workers when restarting or upgrading
+synapse, unless you explicitly know it's safe not to.  For instance, restarting
+synapse without restarting all the synchrotrons may result in broken typing
+notifications.</p>
+<p>To manipulate a specific worker, you pass the -w option to synctl:</p>
+<pre><code class="language-sh">synctl -w $CONFIG/workers/worker1.yaml restart
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="setting-up-synapse-with-workers-and-systemd"><a class="header" href="#setting-up-synapse-with-workers-and-systemd">Setting up Synapse with Workers and Systemd</a></h1>
+<p>This is a setup for managing synapse with systemd, including support for
+managing workers. It provides a <code>matrix-synapse</code> service for the master, as
+well as a <code>matrix-synapse-worker@</code> service template for any workers you
+require. Additionally, to group the required services, it sets up a
+<code>matrix-synapse.target</code>.</p>
+<p>See the folder <a href="https://github.com/matrix-org/synapse/tree/develop/docs/systemd-with-workers/system/">system</a>
+for the systemd unit files.</p>
+<p>The folder <a href="https://github.com/matrix-org/synapse/tree/develop/docs/systemd-with-workers/workers/">workers</a>
+contains an example configuration for the <code>federation_reader</code> worker.</p>
+<h2 id="synapse-configuration-files"><a class="header" href="#synapse-configuration-files">Synapse configuration files</a></h2>
+<p>See <a href="systemd-with-workers/../workers.html">the worker documentation</a> for information on how to set up the
+configuration files and reverse-proxy correctly.
+Below is a sample <code>federation_reader</code> worker configuration file.</p>
+<pre><code class="language-yaml">worker_app: synapse.app.federation_reader
+worker_name: federation_reader1
+
+worker_replication_host: 127.0.0.1
+worker_replication_http_port: 9093
+
+worker_listeners:
+    - type: http
+      port: 8011
+      resources:
+          - names: [federation]
+
+worker_log_config: /etc/matrix-synapse/federation-reader-log.yaml
+</code></pre>
+<p>Systemd manages daemonization itself, so ensure that none of the configuration
+files set either <code>daemonize</code> or <code>worker_daemonize</code>.</p>
+<p>The config files of all workers are expected to be located in
+<code>/etc/matrix-synapse/workers</code>. If you want to use a different location, edit
+the provided <code>*.service</code> files accordingly.</p>
+<p>There is no need for a separate configuration file for the master process.</p>
+<h2 id="set-up"><a class="header" href="#set-up">Set up</a></h2>
+<ol>
+<li>Adjust synapse configuration files as above.</li>
+<li>Copy the <code>*.service</code> and <code>*.target</code> files in <a href="https://github.com/matrix-org/synapse/tree/develop/docs/systemd-with-workers/system/">system</a>
+to <code>/etc/systemd/system</code>.</li>
+<li>Run <code>systemctl daemon-reload</code> to tell systemd to load the new unit files.</li>
+<li>Run <code>systemctl enable matrix-synapse.service</code>. This will configure the
+synapse master process to be started as part of the <code>matrix-synapse.target</code>
+target.</li>
+<li>For each worker process to be enabled, run <code>systemctl enable matrix-synapse-worker@&lt;worker_name&gt;.service</code>. For each <code>&lt;worker_name&gt;</code>, there
+should be a corresponding configuration file.
+<code>/etc/matrix-synapse/workers/&lt;worker_name&gt;.yaml</code>.</li>
+<li>Start all the synapse processes with <code>systemctl start matrix-synapse.target</code>.</li>
+<li>Tell systemd to start synapse on boot with <code>systemctl enable matrix-synapse.target</code>.</li>
+</ol>
+<h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
+<p>Once the services are correctly set up, you can use the following commands
+to manage your synapse installation:</p>
+<pre><code class="language-sh"># Restart Synapse master and all workers
+systemctl restart matrix-synapse.target
+
+# Stop Synapse and all workers
+systemctl stop matrix-synapse.target
+
+# Restart the master alone
+systemctl start matrix-synapse.service
+
+# Restart a specific worker (eg. federation_reader); the master is
+# unaffected by this.
+systemctl restart matrix-synapse-worker@federation_reader.service
+
+# Add a new worker (assuming all configs are set up already)
+systemctl enable matrix-synapse-worker@federation_writer.service
+systemctl restart matrix-synapse.target
+</code></pre>
+<h2 id="hardening"><a class="header" href="#hardening">Hardening</a></h2>
+<p><strong>Optional:</strong> If further hardening is desired, the file
+<code>override-hardened.conf</code> may be copied from
+<a href="https://github.com/matrix-org/synapse/tree/develop/contrib/systemd/">contrib/systemd/override-hardened.conf</a>
+in this repository to the location
+<code>/etc/systemd/system/matrix-synapse.service.d/override-hardened.conf</code> (the
+directory may have to be created). It enables certain sandboxing features in
+systemd to further secure the synapse service. You may read the comments to
+understand what the override file is doing. The same file will need to be copied to
+<code>/etc/systemd/system/matrix-synapse-worker@.service.d/override-hardened-worker.conf</code>
+(this directory may also have to be created) in order to apply the same
+hardening options to any worker processes.</p>
+<p>Once these files have been copied to their appropriate locations, simply reload
+systemd's manager config files and restart all Synapse services to apply the hardening options. They will automatically
+be applied at every restart as long as the override files are present at the
+specified locations.</p>
+<pre><code class="language-sh">systemctl daemon-reload
+
+# Restart services
+systemctl restart matrix-synapse.target
+</code></pre>
+<p>In order to see their effect, you may run <code>systemd-analyze security matrix-synapse.service</code> before and after applying the hardening options to see
+the changes being applied at a glance.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="administration"><a class="header" href="#administration">Administration</a></h1>
+<p>This section contains information on managing your Synapse homeserver. This includes:</p>
+<ul>
+<li>Managing users, rooms and media via the Admin API.</li>
+<li>Setting up metrics and monitoring to give you insight into your homeserver's health.</li>
+<li>Configuring structured logging.</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="the-admin-api"><a class="header" href="#the-admin-api">The Admin API</a></h1>
+<h2 id="authenticate-as-a-server-admin"><a class="header" href="#authenticate-as-a-server-admin">Authenticate as a server admin</a></h2>
+<p>Many of the API calls in the admin api will require an <code>access_token</code> for a
+server admin. (Note that a server admin is distinct from a room admin.)</p>
+<p>A user can be marked as a server admin by updating the database directly, e.g.:</p>
+<pre><code class="language-sql">UPDATE users SET admin = 1 WHERE name = '@foo:bar.com';
+</code></pre>
+<p>A new server admin user can also be created using the <code>register_new_matrix_user</code>
+command. This is a script that is distributed as part of synapse. It is possibly
+already on your <code>$PATH</code> depending on how Synapse was installed.</p>
+<p>Finding your user's <code>access_token</code> is client-dependent, but will usually be shown in the client's settings.</p>
+<h2 id="making-an-admin-api-request"><a class="header" href="#making-an-admin-api-request">Making an Admin API request</a></h2>
+<p>Once you have your <code>access_token</code>, you will need to authenticate each request to an Admin API endpoint by
+providing the token as either a query parameter or a request header. To add it as a request header in cURL:</p>
+<pre><code class="language-sh">curl --header &quot;Authorization: Bearer &lt;access_token&gt;&quot; &lt;the_rest_of_your_API_request&gt;
+</code></pre>
+<p>For more details on access tokens in Matrix, please refer to the complete
+<a href="https://matrix.org/docs/spec/client_server/r0.6.1#using-access-tokens">matrix spec documentation</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="account-validity-api"><a class="header" href="#account-validity-api">Account validity API</a></h1>
+<p>This API allows a server administrator to manage the validity of an account. To
+use it, you must enable the account validity feature (under
+<code>account_validity</code>) in Synapse's configuration.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<h2 id="renew-account"><a class="header" href="#renew-account">Renew account</a></h2>
+<p>This API extends the validity of an account by as much time as configured in the
+<code>period</code> parameter from the <code>account_validity</code> configuration.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/account_validity/validity
+</code></pre>
+<p>with the following body:</p>
+<pre><code class="language-json">{
+    &quot;user_id&quot;: &quot;&lt;user ID for the account to renew&gt;&quot;,
+    &quot;expiration_ts&quot;: 0,
+    &quot;enable_renewal_emails&quot;: true
+}
+</code></pre>
+<p><code>expiration_ts</code> is an optional parameter and overrides the expiration date,
+which otherwise defaults to now + validity period.</p>
+<p><code>enable_renewal_emails</code> is also an optional parameter and enables/disables
+sending renewal emails to the user. Defaults to true.</p>
+<p>The API returns with the new expiration date for this account, as a timestamp in
+milliseconds since epoch:</p>
+<pre><code class="language-json">{
+    &quot;expiration_ts&quot;: 0
+}
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="background-updates-api"><a class="header" href="#background-updates-api">Background Updates API</a></h1>
+<p>This API allows a server administrator to manage the background updates being
+run against the database.</p>
+<h2 id="status"><a class="header" href="#status">Status</a></h2>
+<p>This API gets the current status of the background updates.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/background_updates/status
+</code></pre>
+<p>Returning:</p>
+<pre><code class="language-json">{
+    &quot;enabled&quot;: true,
+    &quot;current_updates&quot;: {
+        &quot;&lt;db_name&gt;&quot;: {
+            &quot;name&quot;: &quot;&lt;background_update_name&gt;&quot;,
+            &quot;total_item_count&quot;: 50,
+            &quot;total_duration_ms&quot;: 10000.0,
+            &quot;average_items_per_ms&quot;: 2.2,
+        },
+    }
+}
+</code></pre>
+<p><code>enabled</code> whether the background updates are enabled or disabled.</p>
+<p><code>db_name</code> the database name (usually Synapse is configured with a single database named 'master').</p>
+<p>For each update:</p>
+<p><code>name</code> the name of the update.
+<code>total_item_count</code> total number of &quot;items&quot; processed (the meaning of 'items' depends on the update in question).
+<code>total_duration_ms</code> how long the background process has been running, not including time spent sleeping.
+<code>average_items_per_ms</code> how many items are processed per millisecond based on an exponential average.</p>
+<h2 id="enabled"><a class="header" href="#enabled">Enabled</a></h2>
+<p>This API allow pausing background updates.</p>
+<p>Background updates should <em>not</em> be paused for significant periods of time, as
+this can affect the performance of Synapse.</p>
+<p><em>Note</em>: This won't persist over restarts.</p>
+<p><em>Note</em>: This won't cancel any update query that is currently running. This is
+usually fine since most queries are short lived, except for <code>CREATE INDEX</code>
+background updates which won't be cancelled once started.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/background_updates/enabled
+</code></pre>
+<p>with the following body:</p>
+<pre><code class="language-json">{
+    &quot;enabled&quot;: false
+}
+</code></pre>
+<p><code>enabled</code> sets whether the background updates are enabled or disabled.</p>
+<p>The API returns the <code>enabled</code> param.</p>
+<pre><code class="language-json">{
+    &quot;enabled&quot;: false
+}
+</code></pre>
+<p>There is also a <code>GET</code> version which returns the <code>enabled</code> state.</p>
+<h2 id="run"><a class="header" href="#run">Run</a></h2>
+<p>This API schedules a specific background update to run. The job starts immediately after calling the API.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/background_updates/start_job
+</code></pre>
+<p>with the following body:</p>
+<pre><code class="language-json">{
+    &quot;job_name&quot;: &quot;populate_stats_process_rooms&quot;
+}
+</code></pre>
+<p>The following JSON body parameters are available:</p>
+<ul>
+<li><code>job_name</code> - A string which job to run. Valid values are:
+<ul>
+<li><code>populate_stats_process_rooms</code> - Recalculate the stats for all rooms.</li>
+<li><code>regenerate_directory</code> - Recalculate the <a href="usage/administration/admin_api/../../../user_directory.html">user directory</a> if it is stale or out of sync.</li>
+</ul>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="delete-a-local-group"><a class="header" href="#delete-a-local-group">Delete a local group</a></h1>
+<p>This API lets a server admin delete a local group. Doing so will kick all
+users out of the group so that their clients will correctly handle the group
+being deleted.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/delete_group/&lt;group_id&gt;
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="show-reported-events"><a class="header" href="#show-reported-events">Show reported events</a></h1>
+<p>This API returns information about reported events.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<p>The api is:</p>
+<pre><code>GET /_synapse/admin/v1/event_reports?from=0&amp;limit=10
+</code></pre>
+<p>It returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+    &quot;event_reports&quot;: [
+        {
+            &quot;event_id&quot;: &quot;$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY&quot;,
+            &quot;id&quot;: 2,
+            &quot;reason&quot;: &quot;foo&quot;,
+            &quot;score&quot;: -100,
+            &quot;received_ts&quot;: 1570897107409,
+            &quot;canonical_alias&quot;: &quot;#alias1:matrix.org&quot;,
+            &quot;room_id&quot;: &quot;!ERAgBpSOcCCuTJqQPk:matrix.org&quot;,
+            &quot;name&quot;: &quot;Matrix HQ&quot;,
+            &quot;sender&quot;: &quot;@foobar:matrix.org&quot;,
+            &quot;user_id&quot;: &quot;@foo:matrix.org&quot;
+        },
+        {
+            &quot;event_id&quot;: &quot;$3IcdZsDaN_En-S1DF4EMCy3v4gNRKeOJs8W5qTOKj4I&quot;,
+            &quot;id&quot;: 3,
+            &quot;reason&quot;: &quot;bar&quot;,
+            &quot;score&quot;: -100,
+            &quot;received_ts&quot;: 1598889612059,
+            &quot;canonical_alias&quot;: &quot;#alias2:matrix.org&quot;,
+            &quot;room_id&quot;: &quot;!eGvUQuTCkHGVwNMOjv:matrix.org&quot;,
+            &quot;name&quot;: &quot;Your room name here&quot;,
+            &quot;sender&quot;: &quot;@foobar:matrix.org&quot;,
+            &quot;user_id&quot;: &quot;@bar:matrix.org&quot;
+        }
+    ],
+    &quot;next_token&quot;: 2,
+    &quot;total&quot;: 4
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint again with <code>from</code>
+set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more reports to
+paginate through.</p>
+<p><strong>URL parameters:</strong></p>
+<ul>
+<li><code>limit</code>: integer - Is optional but is used for pagination, denoting the maximum number
+of items to return in this call. Defaults to <code>100</code>.</li>
+<li><code>from</code>: integer - Is optional but used for pagination, denoting the offset in the
+returned results. This should be treated as an opaque value and not explicitly set to
+anything other than the return value of <code>next_token</code> from a previous call. Defaults to <code>0</code>.</li>
+<li><code>dir</code>: string - Direction of event report order. Whether to fetch the most recent
+first (<code>b</code>) or the oldest first (<code>f</code>). Defaults to <code>b</code>.</li>
+<li><code>user_id</code>: string - Is optional and filters to only return users with user IDs that
+contain this value. This is the user who reported the event and wrote the reason.</li>
+<li><code>room_id</code>: string - Is optional and filters to only return rooms with room IDs that
+contain this value.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>id</code>: integer - ID of event report.</li>
+<li><code>received_ts</code>: integer - The timestamp (in milliseconds since the unix epoch) when this
+report was sent.</li>
+<li><code>room_id</code>: string - The ID of the room in which the event being reported is located.</li>
+<li><code>name</code>: string - The name of the room.</li>
+<li><code>event_id</code>: string - The ID of the reported event.</li>
+<li><code>user_id</code>: string - This is the user who reported the event and wrote the reason.</li>
+<li><code>reason</code>: string - Comment made by the <code>user_id</code> in this report. May be blank or <code>null</code>.</li>
+<li><code>score</code>: integer - Content is reported based upon a negative score, where -100 is
+&quot;most offensive&quot; and 0 is &quot;inoffensive&quot;. May be <code>null</code>.</li>
+<li><code>sender</code>: string - This is the ID of the user who sent the original message/event that
+was reported.</li>
+<li><code>canonical_alias</code>: string - The canonical alias of the room. <code>null</code> if the room does not
+have a canonical alias set.</li>
+<li><code>next_token</code>: integer - Indication for pagination. See above.</li>
+<li><code>total</code>: integer - Total number of event reports related to the query
+(<code>user_id</code> and <code>room_id</code>).</li>
+</ul>
+<h1 id="show-details-of-a-specific-event-report"><a class="header" href="#show-details-of-a-specific-event-report">Show details of a specific event report</a></h1>
+<p>This API returns information about a specific event report.</p>
+<p>The api is:</p>
+<pre><code>GET /_synapse/admin/v1/event_reports/&lt;report_id&gt;
+</code></pre>
+<p>It returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+    &quot;event_id&quot;: &quot;$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY&quot;,
+    &quot;event_json&quot;: {
+        &quot;auth_events&quot;: [
+            &quot;$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M&quot;,
+            &quot;$oggsNXxzPFRE3y53SUNd7nsj69-QzKv03a1RucHu-ws&quot;
+        ],
+        &quot;content&quot;: {
+            &quot;body&quot;: &quot;matrix.org: This Week in Matrix&quot;,
+            &quot;format&quot;: &quot;org.matrix.custom.html&quot;,
+            &quot;formatted_body&quot;: &quot;&lt;strong&gt;matrix.org&lt;/strong&gt;:&lt;br&gt;&lt;a href=\&quot;https://matrix.org/blog/\&quot;&gt;&lt;strong&gt;This Week in Matrix&lt;/strong&gt;&lt;/a&gt;&quot;,
+            &quot;msgtype&quot;: &quot;m.notice&quot;
+        },
+        &quot;depth&quot;: 546,
+        &quot;hashes&quot;: {
+            &quot;sha256&quot;: &quot;xK1//xnmvHJIOvbgXlkI8eEqdvoMmihVDJ9J4SNlsAw&quot;
+        },
+        &quot;origin&quot;: &quot;matrix.org&quot;,
+        &quot;origin_server_ts&quot;: 1592291711430,
+        &quot;prev_events&quot;: [
+            &quot;$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M&quot;
+        ],
+        &quot;prev_state&quot;: [],
+        &quot;room_id&quot;: &quot;!ERAgBpSOcCCuTJqQPk:matrix.org&quot;,
+        &quot;sender&quot;: &quot;@foobar:matrix.org&quot;,
+        &quot;signatures&quot;: {
+            &quot;matrix.org&quot;: {
+                &quot;ed25519:a_JaEG&quot;: &quot;cs+OUKW/iHx5pEidbWxh0UiNNHwe46Ai9LwNz+Ah16aWDNszVIe2gaAcVZfvNsBhakQTew51tlKmL2kspXk/Dg&quot;
+            }
+        },
+        &quot;type&quot;: &quot;m.room.message&quot;,
+        &quot;unsigned&quot;: {
+            &quot;age_ts&quot;: 1592291711430
+        }
+    },
+    &quot;id&quot;: &lt;report_id&gt;,
+    &quot;reason&quot;: &quot;foo&quot;,
+    &quot;score&quot;: -100,
+    &quot;received_ts&quot;: 1570897107409,
+    &quot;canonical_alias&quot;: &quot;#alias1:matrix.org&quot;,
+    &quot;room_id&quot;: &quot;!ERAgBpSOcCCuTJqQPk:matrix.org&quot;,
+    &quot;name&quot;: &quot;Matrix HQ&quot;,
+    &quot;sender&quot;: &quot;@foobar:matrix.org&quot;,
+    &quot;user_id&quot;: &quot;@foo:matrix.org&quot;
+}
+</code></pre>
+<p><strong>URL parameters:</strong></p>
+<ul>
+<li><code>report_id</code>: string - The ID of the event report.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>id</code>: integer - ID of event report.</li>
+<li><code>received_ts</code>: integer - The timestamp (in milliseconds since the unix epoch) when this
+report was sent.</li>
+<li><code>room_id</code>: string - The ID of the room in which the event being reported is located.</li>
+<li><code>name</code>: string - The name of the room.</li>
+<li><code>event_id</code>: string - The ID of the reported event.</li>
+<li><code>user_id</code>: string - This is the user who reported the event and wrote the reason.</li>
+<li><code>reason</code>: string - Comment made by the <code>user_id</code> in this report. May be blank.</li>
+<li><code>score</code>: integer - Content is reported based upon a negative score, where -100 is
+&quot;most offensive&quot; and 0 is &quot;inoffensive&quot;.</li>
+<li><code>sender</code>: string - This is the ID of the user who sent the original message/event that
+was reported.</li>
+<li><code>canonical_alias</code>: string - The canonical alias of the room. <code>null</code> if the room does not
+have a canonical alias set.</li>
+<li><code>event_json</code>: object - Details of the original event that was reported.</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="querying-media"><a class="header" href="#querying-media">Querying media</a></h1>
+<p>These APIs allow extracting media information from the homeserver.</p>
+<p>Details about the format of the <code>media_id</code> and storage of the media in the file system
+are documented under <a href="admin_api/../media_repository.html">media repository</a>.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<h2 id="list-all-media-in-a-room"><a class="header" href="#list-all-media-in-a-room">List all media in a room</a></h2>
+<p>This API gets a list of known media in a room.
+However, it only shows media from unencrypted events or rooms.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/room/&lt;room_id&gt;/media
+</code></pre>
+<p>The API returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+  &quot;local&quot;: [
+    &quot;mxc://localhost/xwvutsrqponmlkjihgfedcba&quot;,
+    &quot;mxc://localhost/abcdefghijklmnopqrstuvwx&quot;
+  ],
+  &quot;remote&quot;: [
+    &quot;mxc://matrix.org/xwvutsrqponmlkjihgfedcba&quot;,
+    &quot;mxc://matrix.org/abcdefghijklmnopqrstuvwx&quot;
+  ]
+}
+</code></pre>
+<h2 id="list-all-media-uploaded-by-a-user"><a class="header" href="#list-all-media-uploaded-by-a-user">List all media uploaded by a user</a></h2>
+<p>Listing all media that has been uploaded by a local user can be achieved through
+the use of the
+<a href="admin_api/user_admin_api.html#list-media-uploaded-by-a-user">List media uploaded by a user</a>
+Admin API.</p>
+<h1 id="quarantine-media"><a class="header" href="#quarantine-media">Quarantine media</a></h1>
+<p>Quarantining media means that it is marked as inaccessible by users. It applies
+to any local media, and any locally-cached copies of remote media.</p>
+<p>The media file itself (and any thumbnails) is not deleted from the server.</p>
+<h2 id="quarantining-media-by-id"><a class="header" href="#quarantining-media-by-id">Quarantining media by ID</a></h2>
+<p>This API quarantines a single piece of local or remote media.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/media/quarantine/&lt;server_name&gt;/&lt;media_id&gt;
+
+{}
+</code></pre>
+<p>Where <code>server_name</code> is in the form of <code>example.org</code>, and <code>media_id</code> is in the
+form of <code>abcdefg12345...</code>.</p>
+<p>Response:</p>
+<pre><code class="language-json">{}
+</code></pre>
+<h2 id="remove-media-from-quarantine-by-id"><a class="header" href="#remove-media-from-quarantine-by-id">Remove media from quarantine by ID</a></h2>
+<p>This API removes a single piece of local or remote media from quarantine.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/media/unquarantine/&lt;server_name&gt;/&lt;media_id&gt;
+
+{}
+</code></pre>
+<p>Where <code>server_name</code> is in the form of <code>example.org</code>, and <code>media_id</code> is in the
+form of <code>abcdefg12345...</code>.</p>
+<p>Response:</p>
+<pre><code class="language-json">{}
+</code></pre>
+<h2 id="quarantining-media-in-a-room"><a class="header" href="#quarantining-media-in-a-room">Quarantining media in a room</a></h2>
+<p>This API quarantines all local and remote media in a room.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/room/&lt;room_id&gt;/media/quarantine
+
+{}
+</code></pre>
+<p>Where <code>room_id</code> is in the form of <code>!roomid12345:example.org</code>.</p>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;num_quarantined&quot;: 10
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>num_quarantined</code>: integer - The number of media items successfully quarantined</li>
+</ul>
+<p>Note that there is a legacy endpoint, <code>POST /_synapse/admin/v1/quarantine_media/&lt;room_id&gt;</code>, that operates the same.
+However, it is deprecated and may be removed in a future release.</p>
+<h2 id="quarantining-all-media-of-a-user"><a class="header" href="#quarantining-all-media-of-a-user">Quarantining all media of a user</a></h2>
+<p>This API quarantines all <em>local</em> media that a <em>local</em> user has uploaded. That is to say, if
+you would like to quarantine media uploaded by a user on a remote homeserver, you should
+instead use one of the other APIs.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/user/&lt;user_id&gt;/media/quarantine
+
+{}
+</code></pre>
+<p>URL Parameters</p>
+<ul>
+<li><code>user_id</code>: string - User ID in the form of <code>@bob:example.org</code></li>
+</ul>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;num_quarantined&quot;: 10
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>num_quarantined</code>: integer - The number of media items successfully quarantined</li>
+</ul>
+<h2 id="protecting-media-from-being-quarantined"><a class="header" href="#protecting-media-from-being-quarantined">Protecting media from being quarantined</a></h2>
+<p>This API protects a single piece of local media from being quarantined using the
+above APIs. This is useful for sticker packs and other shared media which you do
+not want to get quarantined, especially when
+<a href="admin_api/media_admin_api.html#quarantining-media-in-a-room">quarantining media in a room</a>.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/media/protect/&lt;media_id&gt;
+
+{}
+</code></pre>
+<p>Where <code>media_id</code> is in the  form of <code>abcdefg12345...</code>.</p>
+<p>Response:</p>
+<pre><code class="language-json">{}
+</code></pre>
+<h2 id="unprotecting-media-from-being-quarantined"><a class="header" href="#unprotecting-media-from-being-quarantined">Unprotecting media from being quarantined</a></h2>
+<p>This API reverts the protection of a media.</p>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/media/unprotect/&lt;media_id&gt;
+
+{}
+</code></pre>
+<p>Where <code>media_id</code> is in the  form of <code>abcdefg12345...</code>.</p>
+<p>Response:</p>
+<pre><code class="language-json">{}
+</code></pre>
+<h1 id="delete-local-media"><a class="header" href="#delete-local-media">Delete local media</a></h1>
+<p>This API deletes the <em>local</em> media from the disk of your own server.
+This includes any local thumbnails and copies of media downloaded from
+remote homeservers.
+This API will not affect media that has been uploaded to external
+media repositories (e.g https://github.com/turt2live/matrix-media-repo/).
+See also <a href="admin_api/media_admin_api.html#purge-remote-media-api">Purge Remote Media API</a>.</p>
+<h2 id="delete-a-specific-local-media"><a class="header" href="#delete-a-specific-local-media">Delete a specific local media</a></h2>
+<p>Delete a specific <code>media_id</code>.</p>
+<p>Request:</p>
+<pre><code>DELETE /_synapse/admin/v1/media/&lt;server_name&gt;/&lt;media_id&gt;
+
+{}
+</code></pre>
+<p>URL Parameters</p>
+<ul>
+<li><code>server_name</code>: string - The name of your local server (e.g <code>matrix.org</code>)</li>
+<li><code>media_id</code>: string - The ID of the media (e.g <code>abcdefghijklmnopqrstuvwx</code>)</li>
+</ul>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;deleted_media&quot;: [
+    &quot;abcdefghijklmnopqrstuvwx&quot;
+  ],
+  &quot;total&quot;: 1
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>deleted_media</code>: an array of strings - List of deleted <code>media_id</code></li>
+<li><code>total</code>: integer - Total number of deleted <code>media_id</code></li>
+</ul>
+<h2 id="delete-local-media-by-date-or-size"><a class="header" href="#delete-local-media-by-date-or-size">Delete local media by date or size</a></h2>
+<p>Request:</p>
+<pre><code>POST /_synapse/admin/v1/media/&lt;server_name&gt;/delete?before_ts=&lt;before_ts&gt;
+
+{}
+</code></pre>
+<p>URL Parameters</p>
+<ul>
+<li><code>server_name</code>: string - The name of your local server (e.g <code>matrix.org</code>).</li>
+<li><code>before_ts</code>: string representing a positive integer - Unix timestamp in milliseconds.
+Files that were last used before this timestamp will be deleted. It is the timestamp of
+last access, not the timestamp when the file was created.</li>
+<li><code>size_gt</code>: Optional - string representing a positive integer - Size of the media in bytes.
+Files that are larger will be deleted. Defaults to <code>0</code>.</li>
+<li><code>keep_profiles</code>: Optional - string representing a boolean - Switch to also delete files
+that are still used in image data (e.g user profile, room avatar).
+If <code>false</code> these files will be deleted. Defaults to <code>true</code>.</li>
+</ul>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;deleted_media&quot;: [
+    &quot;abcdefghijklmnopqrstuvwx&quot;,
+    &quot;abcdefghijklmnopqrstuvwz&quot;
+  ],
+  &quot;total&quot;: 2
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>deleted_media</code>: an array of strings - List of deleted <code>media_id</code></li>
+<li><code>total</code>: integer - Total number of deleted <code>media_id</code></li>
+</ul>
+<h2 id="delete-media-uploaded-by-a-user"><a class="header" href="#delete-media-uploaded-by-a-user">Delete media uploaded by a user</a></h2>
+<p>You can find details of how to delete multiple media uploaded by a user in
+<a href="admin_api/user_admin_api.html#delete-media-uploaded-by-a-user">User Admin API</a>.</p>
+<h1 id="purge-remote-media-api"><a class="header" href="#purge-remote-media-api">Purge Remote Media API</a></h1>
+<p>The purge remote media API allows server admins to purge old cached remote media.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/purge_media_cache?before_ts=&lt;unix_timestamp_in_ms&gt;
+
+{}
+</code></pre>
+<p>URL Parameters</p>
+<ul>
+<li><code>unix_timestamp_in_ms</code>: string representing a positive integer - Unix timestamp in milliseconds.
+All cached media that was last accessed before this timestamp will be removed.</li>
+</ul>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;deleted&quot;: 10
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>deleted</code>: integer - The number of media items successfully deleted</li>
+</ul>
+<p>If the user re-requests purged remote media, synapse will re-request the media
+from the originating server.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="purge-history-api"><a class="header" href="#purge-history-api">Purge History API</a></h1>
+<p>The purge history API allows server admins to purge historic events from their
+database, reclaiming disk space.</p>
+<p>Depending on the amount of history being purged a call to the API may take
+several minutes or longer. During this period users will not be able to
+paginate further back in the room from the point being purged from.</p>
+<p>Note that Synapse requires at least one message in each room, so it will never
+delete the last message in a room.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/purge_history/&lt;room_id&gt;[/&lt;event_id&gt;]
+</code></pre>
+<p>By default, events sent by local users are not deleted, as they may represent
+the only copies of this content in existence. (Events sent by remote users are
+deleted.)</p>
+<p>Room state data (such as joins, leaves, topic) is always preserved.</p>
+<p>To delete local message events as well, set <code>delete_local_events</code> in the body:</p>
+<pre><code class="language-json">{
+   &quot;delete_local_events&quot;: true
+}
+</code></pre>
+<p>The caller must specify the point in the room to purge up to. This can be
+specified by including an event_id in the URI, or by setting a
+<code>purge_up_to_event_id</code> or <code>purge_up_to_ts</code> in the request body. If an event
+id is given, that event (and others at the same graph depth) will be retained.
+If <code>purge_up_to_ts</code> is given, it should be a timestamp since the unix epoch,
+in milliseconds.</p>
+<p>The API starts the purge running, and returns immediately with a JSON body with
+a purge id:</p>
+<pre><code class="language-json">{
+    &quot;purge_id&quot;: &quot;&lt;opaque id&gt;&quot;
+}
+</code></pre>
+<h2 id="purge-status-query"><a class="header" href="#purge-status-query">Purge status query</a></h2>
+<p>It is possible to poll for updates on recent purges with a second API;</p>
+<pre><code>GET /_synapse/admin/v1/purge_history_status/&lt;purge_id&gt;
+</code></pre>
+<p>This API returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+    &quot;status&quot;: &quot;active&quot;
+}
+</code></pre>
+<p>The status will be one of <code>active</code>, <code>complete</code>, or <code>failed</code>.</p>
+<p>If <code>status</code> is <code>failed</code> there will be a string <code>error</code> with the error message.</p>
+<h2 id="reclaim-disk-space-postgres"><a class="header" href="#reclaim-disk-space-postgres">Reclaim disk space (Postgres)</a></h2>
+<p>To reclaim the disk space and return it to the operating system, you need to run
+<code>VACUUM FULL;</code> on the database.</p>
+<p><a href="https://www.postgresql.org/docs/current/sql-vacuum.html">https://www.postgresql.org/docs/current/sql-vacuum.html</a></p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="shared-secret-registration"><a class="header" href="#shared-secret-registration">Shared-Secret Registration</a></h1>
+<p>This API allows for the creation of users in an administrative and
+non-interactive way. This is generally used for bootstrapping a Synapse
+instance with administrator accounts.</p>
+<p>To authenticate yourself to the server, you will need both the shared secret
+(<code>registration_shared_secret</code> in the homeserver configuration), and a
+one-time nonce. If the registration shared secret is not configured, this API
+is not enabled.</p>
+<p>To fetch the nonce, you need to request one from the API:</p>
+<pre><code>&gt; GET /_synapse/admin/v1/register
+
+&lt; {&quot;nonce&quot;: &quot;thisisanonce&quot;}
+</code></pre>
+<p>Once you have the nonce, you can make a <code>POST</code> to the same URL with a JSON
+body containing the nonce, username, password, whether they are an admin
+(optional, False by default), and a HMAC digest of the content. Also you can
+set the displayname (optional, <code>username</code> by default).</p>
+<p>As an example:</p>
+<pre><code>&gt; POST /_synapse/admin/v1/register
+&gt; {
+   &quot;nonce&quot;: &quot;thisisanonce&quot;,
+   &quot;username&quot;: &quot;pepper_roni&quot;,
+   &quot;displayname&quot;: &quot;Pepper Roni&quot;,
+   &quot;password&quot;: &quot;pizza&quot;,
+   &quot;admin&quot;: true,
+   &quot;mac&quot;: &quot;mac_digest_here&quot;
+  }
+
+&lt; {
+   &quot;access_token&quot;: &quot;token_here&quot;,
+   &quot;user_id&quot;: &quot;@pepper_roni:localhost&quot;,
+   &quot;home_server&quot;: &quot;test&quot;,
+   &quot;device_id&quot;: &quot;device_id_here&quot;
+  }
+</code></pre>
+<p>The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the key being
+the shared secret and the content being the nonce, user, password, either the
+string &quot;admin&quot; or &quot;notadmin&quot;, and optionally the user_type
+each separated by NULs. For an example of generation in Python:</p>
+<pre><code class="language-python">import hmac, hashlib
+
+def generate_mac(nonce, user, password, admin=False, user_type=None):
+
+    mac = hmac.new(
+      key=shared_secret,
+      digestmod=hashlib.sha1,
+    )
+
+    mac.update(nonce.encode('utf8'))
+    mac.update(b&quot;\x00&quot;)
+    mac.update(user.encode('utf8'))
+    mac.update(b&quot;\x00&quot;)
+    mac.update(password.encode('utf8'))
+    mac.update(b&quot;\x00&quot;)
+    mac.update(b&quot;admin&quot; if admin else b&quot;notadmin&quot;)
+    if user_type:
+        mac.update(b&quot;\x00&quot;)
+        mac.update(user_type.encode('utf8'))
+
+    return mac.hexdigest()
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="registration-tokens"><a class="header" href="#registration-tokens">Registration Tokens</a></h1>
+<p>This API allows you to manage tokens which can be used to authenticate
+registration requests, as proposed in
+<a href="https://github.com/matrix-org/matrix-doc/blob/main/proposals/3231-token-authenticated-registration.md">MSC3231</a>.
+To use it, you will need to enable the <code>registration_requires_token</code> config
+option, and authenticate by providing an <code>access_token</code> for a server admin:
+see <a href="usage/administration/admin_api/../../usage/administration/admin_api">Admin API</a>.
+Note that this API is still experimental; not all clients may support it yet.</p>
+<h2 id="registration-token-objects"><a class="header" href="#registration-token-objects">Registration token objects</a></h2>
+<p>Most endpoints make use of JSON objects that contain details about tokens.
+These objects have the following fields:</p>
+<ul>
+<li><code>token</code>: The token which can be used to authenticate registration.</li>
+<li><code>uses_allowed</code>: The number of times the token can be used to complete a
+registration before it becomes invalid.</li>
+<li><code>pending</code>: The number of pending uses the token has. When someone uses
+the token to authenticate themselves, the pending counter is incremented
+so that the token is not used more than the permitted number of times.
+When the person completes registration the pending counter is decremented,
+and the completed counter is incremented.</li>
+<li><code>completed</code>: The number of times the token has been used to successfully
+complete a registration.</li>
+<li><code>expiry_time</code>: The latest time the token is valid. Given as the number of
+milliseconds since 1970-01-01 00:00:00 UTC (the start of the Unix epoch).
+To convert this into a human-readable form you can remove the milliseconds
+and use the <code>date</code> command. For example, <code>date -d '@1625394937'</code>.</li>
+</ul>
+<h2 id="list-all-tokens"><a class="header" href="#list-all-tokens">List all tokens</a></h2>
+<p>Lists all tokens and details about them. If the request is successful, the top
+level JSON object will have a <code>registration_tokens</code> key which is an array of
+registration token objects.</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens
+</code></pre>
+<p>Optional query parameters:</p>
+<ul>
+<li><code>valid</code>: <code>true</code> or <code>false</code>. If <code>true</code>, only valid tokens are returned.
+If <code>false</code>, only tokens that have expired or have had all uses exhausted are
+returned. If omitted, all tokens are returned regardless of validity.</li>
+</ul>
+<p>Example:</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;registration_tokens&quot;: [
+        {
+            &quot;token&quot;: &quot;abcd&quot;,
+            &quot;uses_allowed&quot;: 3,
+            &quot;pending&quot;: 0,
+            &quot;completed&quot;: 1,
+            &quot;expiry_time&quot;: null
+        },
+        {
+            &quot;token&quot;: &quot;pqrs&quot;,
+            &quot;uses_allowed&quot;: 2,
+            &quot;pending&quot;: 1,
+            &quot;completed&quot;: 1,
+            &quot;expiry_time&quot;: null
+        },
+        {
+            &quot;token&quot;: &quot;wxyz&quot;,
+            &quot;uses_allowed&quot;: null,
+            &quot;pending&quot;: 0,
+            &quot;completed&quot;: 9,
+            &quot;expiry_time&quot;: 1625394937000    // 2021-07-04 10:35:37 UTC
+        }
+    ]
+}
+</code></pre>
+<p>Example using the <code>valid</code> query parameter:</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens?valid=false
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;registration_tokens&quot;: [
+        {
+            &quot;token&quot;: &quot;pqrs&quot;,
+            &quot;uses_allowed&quot;: 2,
+            &quot;pending&quot;: 1,
+            &quot;completed&quot;: 1,
+            &quot;expiry_time&quot;: null
+        },
+        {
+            &quot;token&quot;: &quot;wxyz&quot;,
+            &quot;uses_allowed&quot;: null,
+            &quot;pending&quot;: 0,
+            &quot;completed&quot;: 9,
+            &quot;expiry_time&quot;: 1625394937000    // 2021-07-04 10:35:37 UTC
+        }
+    ]
+}
+</code></pre>
+<h2 id="get-one-token"><a class="header" href="#get-one-token">Get one token</a></h2>
+<p>Get details about a single token. If the request is successful, the response
+body will be a registration token object.</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens/&lt;token&gt;
+</code></pre>
+<p>Path parameters:</p>
+<ul>
+<li><code>token</code>: The registration token to return details of.</li>
+</ul>
+<p>Example:</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens/abcd
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;token&quot;: &quot;abcd&quot;,
+    &quot;uses_allowed&quot;: 3,
+    &quot;pending&quot;: 0,
+    &quot;completed&quot;: 1,
+    &quot;expiry_time&quot;: null
+}
+</code></pre>
+<h2 id="create-token"><a class="header" href="#create-token">Create token</a></h2>
+<p>Create a new registration token. If the request is successful, the newly created
+token will be returned as a registration token object in the response body.</p>
+<pre><code>POST /_synapse/admin/v1/registration_tokens/new
+</code></pre>
+<p>The request body must be a JSON object and can contain the following fields:</p>
+<ul>
+<li><code>token</code>: The registration token. A string of no more than 64 characters that
+consists only of characters matched by the regex <code>[A-Za-z0-9._~-]</code>.
+Default: randomly generated.</li>
+<li><code>uses_allowed</code>: The integer number of times the token can be used to complete
+a registration before it becomes invalid.
+Default: <code>null</code> (unlimited uses).</li>
+<li><code>expiry_time</code>: The latest time the token is valid. Given as the number of
+milliseconds since 1970-01-01 00:00:00 UTC (the start of the Unix epoch).
+You could use, for example, <code>date '+%s000' -d 'tomorrow'</code>.
+Default: <code>null</code> (token does not expire).</li>
+<li><code>length</code>: The length of the token randomly generated if <code>token</code> is not
+specified. Must be between 1 and 64 inclusive. Default: <code>16</code>.</li>
+</ul>
+<p>If a field is omitted the default is used.</p>
+<p>Example using defaults:</p>
+<pre><code>POST /_synapse/admin/v1/registration_tokens/new
+
+{}
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;token&quot;: &quot;0M-9jbkf2t_Tgiw1&quot;,
+    &quot;uses_allowed&quot;: null,
+    &quot;pending&quot;: 0,
+    &quot;completed&quot;: 0,
+    &quot;expiry_time&quot;: null
+}
+</code></pre>
+<p>Example specifying some fields:</p>
+<pre><code>POST /_synapse/admin/v1/registration_tokens/new
+
+{
+    &quot;token&quot;: &quot;defg&quot;,
+    &quot;uses_allowed&quot;: 1
+}
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;token&quot;: &quot;defg&quot;,
+    &quot;uses_allowed&quot;: 1,
+    &quot;pending&quot;: 0,
+    &quot;completed&quot;: 0,
+    &quot;expiry_time&quot;: null
+}
+</code></pre>
+<h2 id="update-token"><a class="header" href="#update-token">Update token</a></h2>
+<p>Update the number of allowed uses or expiry time of a token. If the request is
+successful, the updated token will be returned as a registration token object
+in the response body.</p>
+<pre><code>PUT /_synapse/admin/v1/registration_tokens/&lt;token&gt;
+</code></pre>
+<p>Path parameters:</p>
+<ul>
+<li><code>token</code>: The registration token to update.</li>
+</ul>
+<p>The request body must be a JSON object and can contain the following fields:</p>
+<ul>
+<li><code>uses_allowed</code>: The integer number of times the token can be used to complete
+a registration before it becomes invalid. By setting <code>uses_allowed</code> to <code>0</code>
+the token can be easily made invalid without deleting it.
+If <code>null</code> the token will have an unlimited number of uses.</li>
+<li><code>expiry_time</code>: The latest time the token is valid. Given as the number of
+milliseconds since 1970-01-01 00:00:00 UTC (the start of the Unix epoch).
+If <code>null</code> the token will not expire.</li>
+</ul>
+<p>If a field is omitted its value is not modified.</p>
+<p>Example:</p>
+<pre><code>PUT /_synapse/admin/v1/registration_tokens/defg
+
+{
+    &quot;expiry_time&quot;: 4781243146000    // 2121-07-06 11:05:46 UTC
+}
+</code></pre>
+<pre><code>200 OK
+
+{
+    &quot;token&quot;: &quot;defg&quot;,
+    &quot;uses_allowed&quot;: 1,
+    &quot;pending&quot;: 0,
+    &quot;completed&quot;: 0,
+    &quot;expiry_time&quot;: 4781243146000
+}
+</code></pre>
+<h2 id="delete-token"><a class="header" href="#delete-token">Delete token</a></h2>
+<p>Delete a registration token. If the request is successful, the response body
+will be an empty JSON object.</p>
+<pre><code>DELETE /_synapse/admin/v1/registration_tokens/&lt;token&gt;
+</code></pre>
+<p>Path parameters:</p>
+<ul>
+<li><code>token</code>: The registration token to delete.</li>
+</ul>
+<p>Example:</p>
+<pre><code>DELETE /_synapse/admin/v1/registration_tokens/wxyz
+</code></pre>
+<pre><code>200 OK
+
+{}
+</code></pre>
+<h2 id="errors"><a class="header" href="#errors">Errors</a></h2>
+<p>If a request fails a &quot;standard error response&quot; will be returned as defined in
+the <a href="https://matrix.org/docs/spec/client_server/r0.6.1#api-standards">Matrix Client-Server API specification</a>.</p>
+<p>For example, if the token specified in a path parameter does not exist a
+<code>404 Not Found</code> error will be returned.</p>
+<pre><code>GET /_synapse/admin/v1/registration_tokens/1234
+</code></pre>
+<pre><code>404 Not Found
+
+{
+    &quot;errcode&quot;: &quot;M_NOT_FOUND&quot;,
+    &quot;error&quot;: &quot;No such registration token: 1234&quot;
+}
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="edit-room-membership-api"><a class="header" href="#edit-room-membership-api">Edit Room Membership API</a></h1>
+<p>This API allows an administrator to join an user account with a given <code>user_id</code>
+to a room with a given <code>room_id_or_alias</code>. You can only modify the membership of
+local users. The server administrator must be in the room and have permission to
+invite users.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<h2 id="parameters"><a class="header" href="#parameters">Parameters</a></h2>
+<p>The following parameters are available:</p>
+<ul>
+<li><code>user_id</code> - Fully qualified user: for example, <code>@user:server.com</code>.</li>
+<li><code>room_id_or_alias</code> - The room identifier or alias to join: for example,
+<code>!636q39766251:server.com</code>.</li>
+</ul>
+<h2 id="usage-1"><a class="header" href="#usage-1">Usage</a></h2>
+<pre><code>POST /_synapse/admin/v1/join/&lt;room_id_or_alias&gt;
+
+{
+  &quot;user_id&quot;: &quot;@user:server.com&quot;
+}
+</code></pre>
+<p>Response:</p>
+<pre><code class="language-json">{
+  &quot;room_id&quot;: &quot;!636q39766251:server.com&quot;
+}
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="list-room-api"><a class="header" href="#list-room-api">List Room API</a></h1>
+<p>The List Room admin API allows server admins to get a list of rooms on their
+server. There are various parameters available that allow for filtering and
+sorting the returned list. This API supports pagination.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<p><strong>Parameters</strong></p>
+<p>The following query parameters are available:</p>
+<ul>
+<li>
+<p><code>from</code> - Offset in the returned list. Defaults to <code>0</code>.</p>
+</li>
+<li>
+<p><code>limit</code> - Maximum amount of rooms to return. Defaults to <code>100</code>.</p>
+</li>
+<li>
+<p><code>order_by</code> - The method in which to sort the returned list of rooms. Valid values are:</p>
+<ul>
+<li><code>alphabetical</code> - Same as <code>name</code>. This is deprecated.</li>
+<li><code>size</code> - Same as <code>joined_members</code>. This is deprecated.</li>
+<li><code>name</code> - Rooms are ordered alphabetically by room name. This is the default.</li>
+<li><code>canonical_alias</code> - Rooms are ordered alphabetically by main alias address of the room.</li>
+<li><code>joined_members</code> - Rooms are ordered by the number of members. Largest to smallest.</li>
+<li><code>joined_local_members</code> - Rooms are ordered by the number of local members. Largest to smallest.</li>
+<li><code>version</code> - Rooms are ordered by room version. Largest to smallest.</li>
+<li><code>creator</code> - Rooms are ordered alphabetically by creator of the room.</li>
+<li><code>encryption</code> - Rooms are ordered alphabetically by the end-to-end encryption algorithm.</li>
+<li><code>federatable</code> - Rooms are ordered by whether the room is federatable.</li>
+<li><code>public</code> - Rooms are ordered by visibility in room list.</li>
+<li><code>join_rules</code> - Rooms are ordered alphabetically by join rules of the room.</li>
+<li><code>guest_access</code> - Rooms are ordered alphabetically by guest access option of the room.</li>
+<li><code>history_visibility</code> - Rooms are ordered alphabetically by visibility of history of the room.</li>
+<li><code>state_events</code> - Rooms are ordered by number of state events. Largest to smallest.</li>
+</ul>
+</li>
+<li>
+<p><code>dir</code> - Direction of room order. Either <code>f</code> for forwards or <code>b</code> for backwards. Setting
+this value to <code>b</code> will reverse the above sort order. Defaults to <code>f</code>.</p>
+</li>
+<li>
+<p><code>search_term</code> - Filter rooms by their room name, canonical alias and room id.
+Specifically, rooms are selected if the search term is contained in</p>
+<ul>
+<li>the room's name,</li>
+<li>the local part of the room's canonical alias, or</li>
+<li>the complete (local and server part) room's id (case sensitive).</li>
+</ul>
+<p>Defaults to no filtering.</p>
+</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are possible in the JSON response body:</p>
+<ul>
+<li><code>rooms</code> - An array of objects, each containing information about a room.
+<ul>
+<li>Room objects contain the following fields:
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+<li><code>name</code> - The name of the room.</li>
+<li><code>canonical_alias</code> - The canonical (main) alias address of the room.</li>
+<li><code>joined_members</code> - How many users are currently in the room.</li>
+<li><code>joined_local_members</code> - How many local users are currently in the room.</li>
+<li><code>version</code> - The version of the room as a string.</li>
+<li><code>creator</code> - The <code>user_id</code> of the room creator.</li>
+<li><code>encryption</code> - Algorithm of end-to-end encryption of messages. Is <code>null</code> if encryption is not active.</li>
+<li><code>federatable</code> - Whether users on other servers can join this room.</li>
+<li><code>public</code> - Whether the room is visible in room directory.</li>
+<li><code>join_rules</code> - The type of rules used for users wishing to join this room. One of: [&quot;public&quot;, &quot;knock&quot;, &quot;invite&quot;, &quot;private&quot;].</li>
+<li><code>guest_access</code> - Whether guests can join the room. One of: [&quot;can_join&quot;, &quot;forbidden&quot;].</li>
+<li><code>history_visibility</code> - Who can see the room history. One of: [&quot;invited&quot;, &quot;joined&quot;, &quot;shared&quot;, &quot;world_readable&quot;].</li>
+<li><code>state_events</code> - Total number of state_events of a room. Complexity of the room.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li><code>offset</code> - The current pagination offset in rooms. This parameter should be
+used instead of <code>next_token</code> for room offset as <code>next_token</code> is
+not intended to be parsed.</li>
+<li><code>total_rooms</code> - The total number of rooms this query can return. Using this
+and <code>offset</code>, you have enough information to know the current
+progression through the list.</li>
+<li><code>next_batch</code> - If this field is present, we know that there are potentially
+more rooms on the server that did not all fit into this response.
+We can use <code>next_batch</code> to get the &quot;next page&quot; of results. To do
+so, simply repeat your request, setting the <code>from</code> parameter to
+the value of <code>next_batch</code>.</li>
+<li><code>prev_batch</code> - If this field is present, it is possible to paginate backwards.
+Use <code>prev_batch</code> for the <code>from</code> value in the next request to
+get the &quot;previous page&quot; of results.</li>
+</ul>
+<p>The API is:</p>
+<p>A standard request with no filtering:</p>
+<pre><code>GET /_synapse/admin/v1/rooms
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;rooms&quot;: [
+    {
+      &quot;room_id&quot;: &quot;!OGEhHVWSdvArJzumhm:matrix.org&quot;,
+      &quot;name&quot;: &quot;Matrix HQ&quot;,
+      &quot;canonical_alias&quot;: &quot;#matrix:matrix.org&quot;,
+      &quot;joined_members&quot;: 8326,
+      &quot;joined_local_members&quot;: 2,
+      &quot;version&quot;: &quot;1&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: null,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: true,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 93534
+    },
+    ... (8 hidden items) ...
+    {
+      &quot;room_id&quot;: &quot;!xYvNcQPhnkrdUmYczI:matrix.org&quot;,
+      &quot;name&quot;: &quot;This Week In Matrix (TWIM)&quot;,
+      &quot;canonical_alias&quot;: &quot;#twim:matrix.org&quot;,
+      &quot;joined_members&quot;: 314,
+      &quot;joined_local_members&quot;: 20,
+      &quot;version&quot;: &quot;4&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: &quot;m.megolm.v1.aes-sha2&quot;,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: false,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 8345
+    }
+  ],
+  &quot;offset&quot;: 0,
+  &quot;total_rooms&quot;: 10
+}
+</code></pre>
+<p>Filtering by room name:</p>
+<pre><code>GET /_synapse/admin/v1/rooms?search_term=TWIM
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;rooms&quot;: [
+    {
+      &quot;room_id&quot;: &quot;!xYvNcQPhnkrdUmYczI:matrix.org&quot;,
+      &quot;name&quot;: &quot;This Week In Matrix (TWIM)&quot;,
+      &quot;canonical_alias&quot;: &quot;#twim:matrix.org&quot;,
+      &quot;joined_members&quot;: 314,
+      &quot;joined_local_members&quot;: 20,
+      &quot;version&quot;: &quot;4&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: &quot;m.megolm.v1.aes-sha2&quot;,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: false,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 8
+    }
+  ],
+  &quot;offset&quot;: 0,
+  &quot;total_rooms&quot;: 1
+}
+</code></pre>
+<p>Paginating through a list of rooms:</p>
+<pre><code>GET /_synapse/admin/v1/rooms?order_by=size
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;rooms&quot;: [
+    {
+      &quot;room_id&quot;: &quot;!OGEhHVWSdvArJzumhm:matrix.org&quot;,
+      &quot;name&quot;: &quot;Matrix HQ&quot;,
+      &quot;canonical_alias&quot;: &quot;#matrix:matrix.org&quot;,
+      &quot;joined_members&quot;: 8326,
+      &quot;joined_local_members&quot;: 2,
+      &quot;version&quot;: &quot;1&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: null,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: true,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 93534
+    },
+    ... (98 hidden items) ...
+    {
+      &quot;room_id&quot;: &quot;!xYvNcQPhnkrdUmYczI:matrix.org&quot;,
+      &quot;name&quot;: &quot;This Week In Matrix (TWIM)&quot;,
+      &quot;canonical_alias&quot;: &quot;#twim:matrix.org&quot;,
+      &quot;joined_members&quot;: 314,
+      &quot;joined_local_members&quot;: 20,
+      &quot;version&quot;: &quot;4&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: &quot;m.megolm.v1.aes-sha2&quot;,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: false,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 8345
+    }
+  ],
+  &quot;offset&quot;: 0,
+  &quot;total_rooms&quot;: 150,
+  &quot;next_token&quot;: 100
+}
+</code></pre>
+<p>The presence of the <code>next_token</code> parameter tells us that there are more rooms
+than returned in this request, and we need to make another request to get them.
+To get the next batch of room results, we repeat our request, setting the <code>from</code>
+parameter to the value of <code>next_token</code>.</p>
+<pre><code>GET /_synapse/admin/v1/rooms?order_by=size&amp;from=100
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;rooms&quot;: [
+    {
+      &quot;room_id&quot;: &quot;!mscvqgqpHYjBGDxNym:matrix.org&quot;,
+      &quot;name&quot;: &quot;Music Theory&quot;,
+      &quot;canonical_alias&quot;: &quot;#musictheory:matrix.org&quot;,
+      &quot;joined_members&quot;: 127,
+      &quot;joined_local_members&quot;: 2,
+      &quot;version&quot;: &quot;1&quot;,
+      &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+      &quot;encryption&quot;: null,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: true,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 93534
+    },
+    ... (48 hidden items) ...
+    {
+      &quot;room_id&quot;: &quot;!twcBhHVdZlQWuuxBhN:termina.org.uk&quot;,
+      &quot;name&quot;: &quot;weechat-matrix&quot;,
+      &quot;canonical_alias&quot;: &quot;#weechat-matrix:termina.org.uk&quot;,
+      &quot;joined_members&quot;: 137,
+      &quot;joined_local_members&quot;: 20,
+      &quot;version&quot;: &quot;4&quot;,
+      &quot;creator&quot;: &quot;@foo:termina.org.uk&quot;,
+      &quot;encryption&quot;: null,
+      &quot;federatable&quot;: true,
+      &quot;public&quot;: true,
+      &quot;join_rules&quot;: &quot;invite&quot;,
+      &quot;guest_access&quot;: null,
+      &quot;history_visibility&quot;: &quot;shared&quot;,
+      &quot;state_events&quot;: 8345
+    }
+  ],
+  &quot;offset&quot;: 100,
+  &quot;prev_batch&quot;: 0,
+  &quot;total_rooms&quot;: 150
+}
+</code></pre>
+<p>Once the <code>next_token</code> parameter is no longer present, we know we've reached the
+end of the list.</p>
+<h1 id="room-details-api"><a class="header" href="#room-details-api">Room Details API</a></h1>
+<p>The Room Details admin API allows server admins to get all details of a room.</p>
+<p>The following fields are possible in the JSON response body:</p>
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+<li><code>name</code> - The name of the room.</li>
+<li><code>topic</code> - The topic of the room.</li>
+<li><code>avatar</code> - The <code>mxc</code> URI to the avatar of the room.</li>
+<li><code>canonical_alias</code> - The canonical (main) alias address of the room.</li>
+<li><code>joined_members</code> - How many users are currently in the room.</li>
+<li><code>joined_local_members</code> - How many local users are currently in the room.</li>
+<li><code>joined_local_devices</code> - How many local devices are currently in the room.</li>
+<li><code>version</code> - The version of the room as a string.</li>
+<li><code>creator</code> - The <code>user_id</code> of the room creator.</li>
+<li><code>encryption</code> - Algorithm of end-to-end encryption of messages. Is <code>null</code> if encryption is not active.</li>
+<li><code>federatable</code> - Whether users on other servers can join this room.</li>
+<li><code>public</code> - Whether the room is visible in room directory.</li>
+<li><code>join_rules</code> - The type of rules used for users wishing to join this room. One of: [&quot;public&quot;, &quot;knock&quot;, &quot;invite&quot;, &quot;private&quot;].</li>
+<li><code>guest_access</code> - Whether guests can join the room. One of: [&quot;can_join&quot;, &quot;forbidden&quot;].</li>
+<li><code>history_visibility</code> - Who can see the room history. One of: [&quot;invited&quot;, &quot;joined&quot;, &quot;shared&quot;, &quot;world_readable&quot;].</li>
+<li><code>state_events</code> - Total number of state_events of a room. Complexity of the room.</li>
+</ul>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id&gt;
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;room_id&quot;: &quot;!mscvqgqpHYjBGDxNym:matrix.org&quot;,
+  &quot;name&quot;: &quot;Music Theory&quot;,
+  &quot;avatar&quot;: &quot;mxc://matrix.org/AQDaVFlbkQoErdOgqWRgiGSV&quot;,
+  &quot;topic&quot;: &quot;Theory, Composition, Notation, Analysis&quot;,
+  &quot;canonical_alias&quot;: &quot;#musictheory:matrix.org&quot;,
+  &quot;joined_members&quot;: 127,
+  &quot;joined_local_members&quot;: 2,
+  &quot;joined_local_devices&quot;: 2,
+  &quot;version&quot;: &quot;1&quot;,
+  &quot;creator&quot;: &quot;@foo:matrix.org&quot;,
+  &quot;encryption&quot;: null,
+  &quot;federatable&quot;: true,
+  &quot;public&quot;: true,
+  &quot;join_rules&quot;: &quot;invite&quot;,
+  &quot;guest_access&quot;: null,
+  &quot;history_visibility&quot;: &quot;shared&quot;,
+  &quot;state_events&quot;: 93534
+}
+</code></pre>
+<h1 id="room-members-api"><a class="header" href="#room-members-api">Room Members API</a></h1>
+<p>The Room Members admin API allows server admins to get a list of all members of a room.</p>
+<p>The response includes the following fields:</p>
+<ul>
+<li><code>members</code> - A list of all the members that are present in the room, represented by their ids.</li>
+<li><code>total</code> - Total number of members in the room.</li>
+</ul>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id&gt;/members
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;members&quot;: [
+    &quot;@foo:matrix.org&quot;,
+    &quot;@bar:matrix.org&quot;,
+    &quot;@foobar:matrix.org&quot;
+  ],
+  &quot;total&quot;: 3
+}
+</code></pre>
+<h1 id="room-state-api"><a class="header" href="#room-state-api">Room State API</a></h1>
+<p>The Room State admin API allows server admins to get a list of all state events in a room.</p>
+<p>The response includes the following fields:</p>
+<ul>
+<li><code>state</code> - The current state of the room at the time of request.</li>
+</ul>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id&gt;/state
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;state&quot;: [
+    {&quot;type&quot;: &quot;m.room.create&quot;, &quot;state_key&quot;: &quot;&quot;, &quot;etc&quot;: true},
+    {&quot;type&quot;: &quot;m.room.power_levels&quot;, &quot;state_key&quot;: &quot;&quot;, &quot;etc&quot;: true},
+    {&quot;type&quot;: &quot;m.room.name&quot;, &quot;state_key&quot;: &quot;&quot;, &quot;etc&quot;: true}
+  ]
+}
+</code></pre>
+<h1 id="block-room-api"><a class="header" href="#block-room-api">Block Room API</a></h1>
+<p>The Block Room admin API allows server admins to block and unblock rooms,
+and query to see if a given room is blocked.
+This API can be used to pre-emptively block a room, even if it's unknown to this
+homeserver. Users will be prevented from joining a blocked room.</p>
+<h2 id="block-or-unblock-a-room"><a class="header" href="#block-or-unblock-a-room">Block or unblock a room</a></h2>
+<p>The API is:</p>
+<pre><code>PUT /_synapse/admin/v1/rooms/&lt;room_id&gt;/block
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;block&quot;: true
+}
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;block&quot;: true
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+</ul>
+<p>The following JSON body parameters are available:</p>
+<ul>
+<li><code>block</code> - If <code>true</code> the room will be blocked and if <code>false</code> the room will be unblocked.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are possible in the JSON response body:</p>
+<ul>
+<li><code>block</code> - A boolean. <code>true</code> if the room is blocked, otherwise <code>false</code></li>
+</ul>
+<h2 id="get-block-status"><a class="header" href="#get-block-status">Get block status</a></h2>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id&gt;/block
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;block&quot;: true,
+    &quot;user_id&quot;: &quot;&lt;user_id&gt;&quot;
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are possible in the JSON response body:</p>
+<ul>
+<li><code>block</code> - A boolean. <code>true</code> if the room is blocked, otherwise <code>false</code></li>
+<li><code>user_id</code> - An optional string. If the room is blocked (<code>block</code> is <code>true</code>) shows
+the user who has add the room to blocking list. Otherwise it is not displayed.</li>
+</ul>
+<h1 id="delete-room-api"><a class="header" href="#delete-room-api">Delete Room API</a></h1>
+<p>The Delete Room admin API allows server admins to remove rooms from the server
+and block these rooms.</p>
+<p>Shuts down a room. Moves all local users and room aliases automatically to a
+new room if <code>new_room_user_id</code> is set. Otherwise local users only
+leave the room without any information.</p>
+<p>The new room will be created with the user specified by the <code>new_room_user_id</code> parameter
+as room administrator and will contain a message explaining what happened. Users invited
+to the new room will have power level <code>-10</code> by default, and thus be unable to speak.</p>
+<p>If <code>block</code> is <code>true</code>, users will be prevented from joining the old room.
+This option can in <a href="admin_api/rooms.html#version-1-old-version">Version 1</a> also be used to pre-emptively
+block a room, even if it's unknown to this homeserver. In this case, the room will be
+blocked, and no further action will be taken. If <code>block</code> is <code>false</code>, attempting to
+delete an unknown room is invalid and will be rejected as a bad request.</p>
+<p>This API will remove all trace of the old room from your database after removing
+all local users. If <code>purge</code> is <code>true</code> (the default), all traces of the old room will
+be removed from your database after removing all local users. If you do not want
+this to happen, set <code>purge</code> to <code>false</code>.
+Depending on the amount of history being purged, a call to the API may take
+several minutes or longer.</p>
+<p>The local server will only have the power to move local user and room aliases to
+the new room. Users on other servers will be unaffected.</p>
+<h2 id="version-1-old-version"><a class="header" href="#version-1-old-version">Version 1 (old version)</a></h2>
+<p>This version works synchronously. That means you only get the response once the server has
+finished the action, which may take a long time. If you request the same action
+a second time, and the server has not finished the first one, the second request will block.
+This is fixed in version 2 of this API. The parameters are the same in both APIs.
+This API will become deprecated in the future.</p>
+<p>The API is:</p>
+<pre><code>DELETE /_synapse/admin/v1/rooms/&lt;room_id&gt;
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;new_room_user_id&quot;: &quot;@someuser:example.com&quot;,
+    &quot;room_name&quot;: &quot;Content Violation Notification&quot;,
+    &quot;message&quot;: &quot;Bad Room has been shutdown due to content violations on this server. Please review our Terms of Service.&quot;,
+    &quot;block&quot;: true,
+    &quot;purge&quot;: true
+}
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;kicked_users&quot;: [
+        &quot;@foobar:example.com&quot;
+    ],
+    &quot;failed_to_kick_users&quot;: [],
+    &quot;local_aliases&quot;: [
+        &quot;#badroom:example.com&quot;,
+        &quot;#evilsaloon:example.com&quot;
+    ],
+    &quot;new_room_id&quot;: &quot;!newroomid:example.com&quot;
+}
+</code></pre>
+<p>The parameters and response values have the same format as
+<a href="admin_api/rooms.html#version-2-new-version">version 2</a> of the API.</p>
+<h2 id="version-2-new-version"><a class="header" href="#version-2-new-version">Version 2 (new version)</a></h2>
+<p><strong>Note</strong>: This API is new, experimental and &quot;subject to change&quot;.</p>
+<p>This version works asynchronously, meaning you get the response from server immediately
+while the server works on that task in background. You can then request the status of the action
+to check if it has completed.</p>
+<p>The API is:</p>
+<pre><code>DELETE /_synapse/admin/v2/rooms/&lt;room_id&gt;
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;new_room_user_id&quot;: &quot;@someuser:example.com&quot;,
+    &quot;room_name&quot;: &quot;Content Violation Notification&quot;,
+    &quot;message&quot;: &quot;Bad Room has been shutdown due to content violations on this server. Please review our Terms of Service.&quot;,
+    &quot;block&quot;: true,
+    &quot;purge&quot;: true
+}
+</code></pre>
+<p>The API starts the shut down and purge running, and returns immediately with a JSON body with
+a purge id:</p>
+<pre><code class="language-json">{
+    &quot;delete_id&quot;: &quot;&lt;opaque id&gt;&quot;
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+</ul>
+<p>The following JSON body parameters are available:</p>
+<ul>
+<li><code>new_room_user_id</code> - Optional. If set, a new room will be created with this user ID
+as the creator and admin, and all users in the old room will be moved into that
+room. If not set, no new room will be created and the users will just be removed
+from the old room. The user ID must be on the local server, but does not necessarily
+have to belong to a registered user.</li>
+<li><code>room_name</code> - Optional. A string representing the name of the room that new users will be
+invited to. Defaults to <code>Content Violation Notification</code></li>
+<li><code>message</code> - Optional. A string containing the first message that will be sent as
+<code>new_room_user_id</code> in the new room. Ideally this will clearly convey why the
+original room was shut down. Defaults to <code>Sharing illegal content on this server is not permitted and rooms in violation will be blocked.</code></li>
+<li><code>block</code> - Optional. If set to <code>true</code>, this room will be added to a blocking list,
+preventing future attempts to join the room. Rooms can be blocked
+even if they're not yet known to the homeserver (only with
+<a href="admin_api/rooms.html#version-1-old-version">Version 1</a> of the API). Defaults to <code>false</code>.</li>
+<li><code>purge</code> - Optional. If set to <code>true</code>, it will remove all traces of the room from your database.
+Defaults to <code>true</code>.</li>
+<li><code>force_purge</code> - Optional, and ignored unless <code>purge</code> is <code>true</code>. If set to <code>true</code>, it
+will force a purge to go ahead even if there are local users still in the room. Do not
+use this unless a regular <code>purge</code> operation fails, as it could leave those users'
+clients in a confused state.</li>
+</ul>
+<p>The JSON body must not be empty. The body must be at least <code>{}</code>.</p>
+<h2 id="status-of-deleting-rooms"><a class="header" href="#status-of-deleting-rooms">Status of deleting rooms</a></h2>
+<p><strong>Note</strong>: This API is new, experimental and &quot;subject to change&quot;.</p>
+<p>It is possible to query the status of the background task for deleting rooms.
+The status can be queried up to 24 hours after completion of the task,
+or until Synapse is restarted (whichever happens first).</p>
+<h3 id="query-by-room_id"><a class="header" href="#query-by-room_id">Query by <code>room_id</code></a></h3>
+<p>With this API you can get the status of all active deletion tasks, and all those completed in the last 24h,
+for the given <code>room_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v2/rooms/&lt;room_id&gt;/delete_status
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;results&quot;: [
+        {
+            &quot;delete_id&quot;: &quot;delete_id1&quot;,
+            &quot;status&quot;: &quot;failed&quot;,
+            &quot;error&quot;: &quot;error message&quot;,
+            &quot;shutdown_room&quot;: {
+                &quot;kicked_users&quot;: [],
+                &quot;failed_to_kick_users&quot;: [],
+                &quot;local_aliases&quot;: [],
+                &quot;new_room_id&quot;: null
+            }
+        }, {
+            &quot;delete_id&quot;: &quot;delete_id2&quot;,
+            &quot;status&quot;: &quot;purging&quot;,
+            &quot;shutdown_room&quot;: {
+                &quot;kicked_users&quot;: [
+                    &quot;@foobar:example.com&quot;
+                ],
+                &quot;failed_to_kick_users&quot;: [],
+                &quot;local_aliases&quot;: [
+                    &quot;#badroom:example.com&quot;,
+                    &quot;#evilsaloon:example.com&quot;
+                ],
+                &quot;new_room_id&quot;: &quot;!newroomid:example.com&quot;
+            }
+        }
+    ]
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>room_id</code> - The ID of the room.</li>
+</ul>
+<h3 id="query-by-delete_id"><a class="header" href="#query-by-delete_id">Query by <code>delete_id</code></a></h3>
+<p>With this API you can get the status of one specific task by <code>delete_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v2/rooms/delete_status/&lt;delete_id&gt;
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;status&quot;: &quot;purging&quot;,
+    &quot;shutdown_room&quot;: {
+        &quot;kicked_users&quot;: [
+            &quot;@foobar:example.com&quot;
+        ],
+        &quot;failed_to_kick_users&quot;: [],
+        &quot;local_aliases&quot;: [
+            &quot;#badroom:example.com&quot;,
+            &quot;#evilsaloon:example.com&quot;
+        ],
+        &quot;new_room_id&quot;: &quot;!newroomid:example.com&quot;
+    }
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>delete_id</code> - The ID for this delete.</li>
+</ul>
+<h3 id="response"><a class="header" href="#response">Response</a></h3>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>results</code> - An array of objects, each containing information about one task.
+This field is omitted from the result when you query by <code>delete_id</code>.
+Task objects contain the following fields:
+<ul>
+<li><code>delete_id</code> - The ID for this purge if you query by <code>room_id</code>.</li>
+<li><code>status</code> - The status will be one of:
+<ul>
+<li><code>shutting_down</code> - The process is removing users from the room.</li>
+<li><code>purging</code> - The process is purging the room and event data from database.</li>
+<li><code>complete</code> - The process has completed successfully.</li>
+<li><code>failed</code> - The process is aborted, an error has occurred.</li>
+</ul>
+</li>
+<li><code>error</code> - A string that shows an error message if <code>status</code> is <code>failed</code>.
+Otherwise this field is hidden.</li>
+<li><code>shutdown_room</code> - An object containing information about the result of shutting down the room.
+<em>Note:</em> The result is shown after removing the room members.
+The delete process can still be running. Please pay attention to the <code>status</code>.
+<ul>
+<li><code>kicked_users</code> - An array of users (<code>user_id</code>) that were kicked.</li>
+<li><code>failed_to_kick_users</code> - An array of users (<code>user_id</code>) that that were not kicked.</li>
+<li><code>local_aliases</code> - An array of strings representing the local aliases that were
+migrated from the old room to the new.</li>
+<li><code>new_room_id</code> - A string representing the room ID of the new room, or <code>null</code> if
+no such room was created.</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+<h2 id="undoing-room-deletions"><a class="header" href="#undoing-room-deletions">Undoing room deletions</a></h2>
+<p><em>Note</em>: This guide may be outdated by the time you read it. By nature of room deletions being performed at the database level,
+the structure can and does change without notice.</p>
+<p>First, it's important to understand that a room deletion is very destructive. Undoing a deletion is not as simple as pretending it
+never happened - work has to be done to move forward instead of resetting the past. In fact, in some cases it might not be possible
+to recover at all:</p>
+<ul>
+<li>If the room was invite-only, your users will need to be re-invited.</li>
+<li>If the room no longer has any members at all, it'll be impossible to rejoin.</li>
+<li>The first user to rejoin will have to do so via an alias on a different
+server (or receive an invite from a user on a different server).</li>
+</ul>
+<p>With all that being said, if you still want to try and recover the room:</p>
+<ol>
+<li>
+<p>If the room was <code>block</code>ed, you must unblock it on your server. This can be
+accomplished as follows:</p>
+<ol>
+<li>For safety reasons, shut down Synapse.</li>
+<li>In the database, run <code>DELETE FROM blocked_rooms WHERE room_id = '!example:example.org';</code>
+<ul>
+<li>For caution: it's recommended to run this in a transaction: <code>BEGIN; DELETE ...;</code>, verify you got 1 result, then <code>COMMIT;</code>.</li>
+<li>The room ID is the same one supplied to the delete room API, not the Content Violation room.</li>
+</ul>
+</li>
+<li>Restart Synapse.</li>
+</ol>
+<p>This step is unnecessary if <code>block</code> was not set.</p>
+</li>
+<li>
+<p>Any room aliases on your server that pointed to the deleted room may have
+been deleted, or redirected to the Content Violation room. These will need
+to be restored manually.</p>
+</li>
+<li>
+<p>Users on your server that were in the deleted room will have been kicked
+from the room. Consider whether you want to update their membership
+(possibly via the <a href="admin_api/room_membership.html">Edit Room Membership API</a>) or let
+them handle rejoining themselves.</p>
+</li>
+<li>
+<p>If <code>new_room_user_id</code> was given, a 'Content Violation' will have been
+created. Consider whether you want to delete that roomm.</p>
+</li>
+</ol>
+<h1 id="make-room-admin-api"><a class="header" href="#make-room-admin-api">Make Room Admin API</a></h1>
+<p>Grants another user the highest power available to a local user who is in the room.
+If the user is not in the room, and it is not publicly joinable, then invite the user.</p>
+<p>By default the server admin (the caller) is granted power, but another user can
+optionally be specified, e.g.:</p>
+<pre><code>POST /_synapse/admin/v1/rooms/&lt;room_id_or_alias&gt;/make_room_admin
+{
+    &quot;user_id&quot;: &quot;@foo:example.com&quot;
+}
+</code></pre>
+<h1 id="forward-extremities-admin-api"><a class="header" href="#forward-extremities-admin-api">Forward Extremities Admin API</a></h1>
+<p>Enables querying and deleting forward extremities from rooms. When a lot of forward
+extremities accumulate in a room, performance can become degraded. For details, see
+<a href="https://github.com/matrix-org/synapse/issues/1760">#1760</a>.</p>
+<h2 id="check-for-forward-extremities"><a class="header" href="#check-for-forward-extremities">Check for forward extremities</a></h2>
+<p>To check the status of forward extremities for a room:</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id_or_alias&gt;/forward_extremities
+</code></pre>
+<p>A response as follows will be returned:</p>
+<pre><code class="language-json">{
+  &quot;count&quot;: 1,
+  &quot;results&quot;: [
+    {
+      &quot;event_id&quot;: &quot;$M5SP266vsnxctfwFgFLNceaCo3ujhRtg_NiiHabcdefgh&quot;,
+      &quot;state_group&quot;: 439,
+      &quot;depth&quot;: 123,
+      &quot;received_ts&quot;: 1611263016761
+    }
+  ]
+}
+</code></pre>
+<h2 id="deleting-forward-extremities"><a class="header" href="#deleting-forward-extremities">Deleting forward extremities</a></h2>
+<p><strong>WARNING</strong>: Please ensure you know what you're doing and have read
+the related issue <a href="https://github.com/matrix-org/synapse/issues/1760">#1760</a>.
+Under no situations should this API be executed as an automated maintenance task!</p>
+<p>If a room has lots of forward extremities, the extra can be
+deleted as follows:</p>
+<pre><code>DELETE /_synapse/admin/v1/rooms/&lt;room_id_or_alias&gt;/forward_extremities
+</code></pre>
+<p>A response as follows will be returned, indicating the amount of forward extremities
+that were deleted.</p>
+<pre><code class="language-json">{
+  &quot;deleted&quot;: 1
+}
+</code></pre>
+<h1 id="event-context-api"><a class="header" href="#event-context-api">Event Context API</a></h1>
+<p>This API lets a client find the context of an event. This is designed primarily to investigate abuse reports.</p>
+<pre><code>GET /_synapse/admin/v1/rooms/&lt;room_id&gt;/context/&lt;event_id&gt;
+</code></pre>
+<p>This API mimmicks <a href="https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-context-eventid">GET /_matrix/client/r0/rooms/{roomId}/context/{eventId}</a>. Please refer to the link for all details on parameters and reseponse.</p>
+<p>Example response:</p>
+<pre><code class="language-json">{
+  &quot;end&quot;: &quot;t29-57_2_0_2&quot;,
+  &quot;events_after&quot;: [
+    {
+      &quot;content&quot;: {
+        &quot;body&quot;: &quot;This is an example text message&quot;,
+        &quot;msgtype&quot;: &quot;m.text&quot;,
+        &quot;format&quot;: &quot;org.matrix.custom.html&quot;,
+        &quot;formatted_body&quot;: &quot;&lt;b&gt;This is an example text message&lt;/b&gt;&quot;
+      },
+      &quot;type&quot;: &quot;m.room.message&quot;,
+      &quot;event_id&quot;: &quot;$143273582443PhrSn:example.org&quot;,
+      &quot;room_id&quot;: &quot;!636q39766251:example.com&quot;,
+      &quot;sender&quot;: &quot;@example:example.org&quot;,
+      &quot;origin_server_ts&quot;: 1432735824653,
+      &quot;unsigned&quot;: {
+        &quot;age&quot;: 1234
+      }
+    }
+  ],
+  &quot;event&quot;: {
+    &quot;content&quot;: {
+      &quot;body&quot;: &quot;filename.jpg&quot;,
+      &quot;info&quot;: {
+        &quot;h&quot;: 398,
+        &quot;w&quot;: 394,
+        &quot;mimetype&quot;: &quot;image/jpeg&quot;,
+        &quot;size&quot;: 31037
+      },
+      &quot;url&quot;: &quot;mxc://example.org/JWEIFJgwEIhweiWJE&quot;,
+      &quot;msgtype&quot;: &quot;m.image&quot;
+    },
+    &quot;type&quot;: &quot;m.room.message&quot;,
+    &quot;event_id&quot;: &quot;$f3h4d129462ha:example.com&quot;,
+    &quot;room_id&quot;: &quot;!636q39766251:example.com&quot;,
+    &quot;sender&quot;: &quot;@example:example.org&quot;,
+    &quot;origin_server_ts&quot;: 1432735824653,
+    &quot;unsigned&quot;: {
+      &quot;age&quot;: 1234
+    }
+  },
+  &quot;events_before&quot;: [
+    {
+      &quot;content&quot;: {
+        &quot;body&quot;: &quot;something-important.doc&quot;,
+        &quot;filename&quot;: &quot;something-important.doc&quot;,
+        &quot;info&quot;: {
+          &quot;mimetype&quot;: &quot;application/msword&quot;,
+          &quot;size&quot;: 46144
+        },
+        &quot;msgtype&quot;: &quot;m.file&quot;,
+        &quot;url&quot;: &quot;mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe&quot;
+      },
+      &quot;type&quot;: &quot;m.room.message&quot;,
+      &quot;event_id&quot;: &quot;$143273582443PhrSn:example.org&quot;,
+      &quot;room_id&quot;: &quot;!636q39766251:example.com&quot;,
+      &quot;sender&quot;: &quot;@example:example.org&quot;,
+      &quot;origin_server_ts&quot;: 1432735824653,
+      &quot;unsigned&quot;: {
+        &quot;age&quot;: 1234
+      }
+    }
+  ],
+  &quot;start&quot;: &quot;t27-54_2_0_2&quot;,
+  &quot;state&quot;: [
+    {
+      &quot;content&quot;: {
+        &quot;creator&quot;: &quot;@example:example.org&quot;,
+        &quot;room_version&quot;: &quot;1&quot;,
+        &quot;m.federate&quot;: true,
+        &quot;predecessor&quot;: {
+          &quot;event_id&quot;: &quot;$something:example.org&quot;,
+          &quot;room_id&quot;: &quot;!oldroom:example.org&quot;
+        }
+      },
+      &quot;type&quot;: &quot;m.room.create&quot;,
+      &quot;event_id&quot;: &quot;$143273582443PhrSn:example.org&quot;,
+      &quot;room_id&quot;: &quot;!636q39766251:example.com&quot;,
+      &quot;sender&quot;: &quot;@example:example.org&quot;,
+      &quot;origin_server_ts&quot;: 1432735824653,
+      &quot;unsigned&quot;: {
+        &quot;age&quot;: 1234
+      },
+      &quot;state_key&quot;: &quot;&quot;
+    },
+    {
+      &quot;content&quot;: {
+        &quot;membership&quot;: &quot;join&quot;,
+        &quot;avatar_url&quot;: &quot;mxc://example.org/SEsfnsuifSDFSSEF&quot;,
+        &quot;displayname&quot;: &quot;Alice Margatroid&quot;
+      },
+      &quot;type&quot;: &quot;m.room.member&quot;,
+      &quot;event_id&quot;: &quot;$143273582443PhrSn:example.org&quot;,
+      &quot;room_id&quot;: &quot;!636q39766251:example.com&quot;,
+      &quot;sender&quot;: &quot;@example:example.org&quot;,
+      &quot;origin_server_ts&quot;: 1432735824653,
+      &quot;unsigned&quot;: {
+        &quot;age&quot;: 1234
+      },
+      &quot;state_key&quot;: &quot;@alice:example.org&quot;
+    }
+  ]
+}
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="server-notices-1"><a class="header" href="#server-notices-1">Server Notices</a></h1>
+<p>The API to send notices is as follows:</p>
+<pre><code>POST /_synapse/admin/v1/send_server_notice
+</code></pre>
+<p>or:</p>
+<pre><code>PUT /_synapse/admin/v1/send_server_notice/{txnId}
+</code></pre>
+<p>You will need to authenticate with an access token for an admin user.</p>
+<p>When using the <code>PUT</code> form, retransmissions with the same transaction ID will be
+ignored in the same way as with <code>PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}</code>.</p>
+<p>The request body should look something like the following:</p>
+<pre><code class="language-json">{
+    &quot;user_id&quot;: &quot;@target_user:server_name&quot;,
+    &quot;content&quot;: {
+        &quot;msgtype&quot;: &quot;m.text&quot;,
+        &quot;body&quot;: &quot;This is my message&quot;
+    }
+}
+</code></pre>
+<p>You can optionally include the following additional parameters:</p>
+<ul>
+<li><code>type</code>: the type of event. Defaults to <code>m.room.message</code>.</li>
+<li><code>state_key</code>: Setting this will result in a state event being sent.</li>
+</ul>
+<p>Once the notice has been sent, the API will return the following response:</p>
+<pre><code class="language-json">{
+    &quot;event_id&quot;: &quot;&lt;event_id&gt;&quot;
+}
+</code></pre>
+<p>Note that server notices must be enabled in <code>homeserver.yaml</code> before this API
+can be used. See <a href="admin_api/../server_notices.html">the server notices documentation</a> for more information.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="users-media-usage-statistics"><a class="header" href="#users-media-usage-statistics">Users' media usage statistics</a></h1>
+<p>Returns information about all local media usage of users. Gives the
+possibility to filter them by time and user.</p>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/statistics/users/media
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;users&quot;: [
+    {
+      &quot;displayname&quot;: &quot;foo_user_0&quot;,
+      &quot;media_count&quot;: 2,
+      &quot;media_length&quot;: 134,
+      &quot;user_id&quot;: &quot;@foo_user_0:test&quot;
+    },
+    {
+      &quot;displayname&quot;: &quot;foo_user_1&quot;,
+      &quot;media_count&quot;: 2,
+      &quot;media_length&quot;: 134,
+      &quot;user_id&quot;: &quot;@foo_user_1:test&quot;
+    }
+  ],
+  &quot;next_token&quot;: 3,
+  &quot;total&quot;: 10
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint
+again with <code>from</code> set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more
+reports to paginate through.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>limit</code>: string representing a positive integer - Is optional but is
+used for pagination, denoting the maximum number of items to return
+in this call. Defaults to <code>100</code>.</li>
+<li><code>from</code>: string representing a positive integer - Is optional but used for pagination,
+denoting the offset in the returned results. This should be treated as an opaque value
+and not explicitly set to anything other than the return value of <code>next_token</code> from a
+previous call. Defaults to <code>0</code>.</li>
+<li><code>order_by</code> - string - The method in which to sort the returned list of users. Valid values are:
+<ul>
+<li><code>user_id</code> - Users are ordered alphabetically by <code>user_id</code>. This is the default.</li>
+<li><code>displayname</code> - Users are ordered alphabetically by <code>displayname</code>.</li>
+<li><code>media_length</code> - Users are ordered by the total size of uploaded media in bytes.
+Smallest to largest.</li>
+<li><code>media_count</code> - Users are ordered by number of uploaded media. Smallest to largest.</li>
+</ul>
+</li>
+<li><code>from_ts</code> - string representing a positive integer - Considers only
+files created at this timestamp or later. Unix timestamp in ms.</li>
+<li><code>until_ts</code> - string representing a positive integer - Considers only
+files created at this timestamp or earlier. Unix timestamp in ms.</li>
+<li><code>search_term</code> - string - Filter users by their user ID localpart <strong>or</strong> displayname.
+The search term can be found in any part of the string.
+Defaults to no filtering.</li>
+<li><code>dir</code> - string - Direction of order. Either <code>f</code> for forwards or <code>b</code> for backwards.
+Setting this value to <code>b</code> will reverse the above sort order. Defaults to <code>f</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>users</code> - An array of objects, each containing information
+about the user and their local media. Objects contain the following fields:
+<ul>
+<li><code>displayname</code> - string - Displayname of this user.</li>
+<li><code>media_count</code> - integer - Number of uploaded media by this user.</li>
+<li><code>media_length</code> - integer - Size of uploaded media in bytes by this user.</li>
+<li><code>user_id</code> - string - Fully-qualified user ID (ex. <code>@user:server.com</code>).</li>
+</ul>
+</li>
+<li><code>next_token</code> - integer - Opaque value used for pagination. See above.</li>
+<li><code>total</code> - integer - Total number of users after filtering.</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="user-admin-api"><a class="header" href="#user-admin-api">User Admin API</a></h1>
+<p>To use it, you will need to authenticate by providing an <code>access_token</code>
+for a server admin: see <a href="admin_api/../usage/administration/admin_api">Admin API</a>.</p>
+<h2 id="query-user-account"><a class="header" href="#query-user-account">Query User Account</a></h2>
+<p>This API returns information about a specific user account.</p>
+<p>The api is:</p>
+<pre><code>GET /_synapse/admin/v2/users/&lt;user_id&gt;
+</code></pre>
+<p>It returns a JSON body like the following:</p>
+<pre><code class="language-jsonc">{
+    &quot;name&quot;: &quot;@user:example.com&quot;,
+    &quot;displayname&quot;: &quot;User&quot;, // can be null if not set
+    &quot;threepids&quot;: [
+        {
+            &quot;medium&quot;: &quot;email&quot;,
+            &quot;address&quot;: &quot;&lt;user_mail_1&gt;&quot;,
+            &quot;added_at&quot;: 1586458409743,
+            &quot;validated_at&quot;: 1586458409743
+        },
+        {
+            &quot;medium&quot;: &quot;email&quot;,
+            &quot;address&quot;: &quot;&lt;user_mail_2&gt;&quot;,
+            &quot;added_at&quot;: 1586458409743,
+            &quot;validated_at&quot;: 1586458409743
+        }
+    ],
+    &quot;avatar_url&quot;: &quot;&lt;avatar_url&gt;&quot;,  // can be null if not set
+    &quot;is_guest&quot;: 0,
+    &quot;admin&quot;: 0,
+    &quot;deactivated&quot;: 0,
+    &quot;shadow_banned&quot;: 0,
+    &quot;creation_ts&quot;: 1560432506,
+    &quot;appservice_id&quot;: null,
+    &quot;consent_server_notice_sent&quot;: null,
+    &quot;consent_version&quot;: null,
+    &quot;external_ids&quot;: [
+        {
+            &quot;auth_provider&quot;: &quot;&lt;provider1&gt;&quot;,
+            &quot;external_id&quot;: &quot;&lt;user_id_provider_1&gt;&quot;
+        },
+        {
+            &quot;auth_provider&quot;: &quot;&lt;provider2&gt;&quot;,
+            &quot;external_id&quot;: &quot;&lt;user_id_provider_2&gt;&quot;
+        }
+    ],
+    &quot;user_type&quot;: null
+}
+</code></pre>
+<p>URL parameters:</p>
+<ul>
+<li><code>user_id</code>: fully-qualified user id: for example, <code>@user:server.com</code>.</li>
+</ul>
+<h2 id="create-or-modify-account"><a class="header" href="#create-or-modify-account">Create or modify Account</a></h2>
+<p>This API allows an administrator to create or modify a user account with a
+specific <code>user_id</code>.</p>
+<p>This api is:</p>
+<pre><code>PUT /_synapse/admin/v2/users/&lt;user_id&gt;
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;password&quot;: &quot;user_password&quot;,
+    &quot;displayname&quot;: &quot;User&quot;,
+    &quot;threepids&quot;: [
+        {
+            &quot;medium&quot;: &quot;email&quot;,
+            &quot;address&quot;: &quot;&lt;user_mail_1&gt;&quot;
+        },
+        {
+            &quot;medium&quot;: &quot;email&quot;,
+            &quot;address&quot;: &quot;&lt;user_mail_2&gt;&quot;
+        }
+    ],
+    &quot;external_ids&quot;: [
+        {
+            &quot;auth_provider&quot;: &quot;&lt;provider1&gt;&quot;,
+            &quot;external_id&quot;: &quot;&lt;user_id_provider_1&gt;&quot;
+        },
+        {
+            &quot;auth_provider&quot;: &quot;&lt;provider2&gt;&quot;,
+            &quot;external_id&quot;: &quot;&lt;user_id_provider_2&gt;&quot;
+        }
+    ],
+    &quot;avatar_url&quot;: &quot;&lt;avatar_url&gt;&quot;,
+    &quot;admin&quot;: false,
+    &quot;deactivated&quot;: false,
+    &quot;user_type&quot;: null
+}
+</code></pre>
+<p>Returns HTTP status code:</p>
+<ul>
+<li><code>201</code> - When a new user object was created.</li>
+<li><code>200</code> - When a user was modified.</li>
+</ul>
+<p>URL parameters:</p>
+<ul>
+<li><code>user_id</code>: fully-qualified user id: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p>Body parameters:</p>
+<ul>
+<li><code>password</code> - string, optional. If provided, the user's password is updated and all
+devices are logged out.</li>
+<li><code>displayname</code> - string, optional, defaults to the value of <code>user_id</code>.</li>
+<li><code>threepids</code> - array, optional, allows setting the third-party IDs (email, msisdn)
+<ul>
+<li><code>medium</code> - string. Kind of third-party ID, either <code>email</code> or <code>msisdn</code>.</li>
+<li><code>address</code> - string. Value of third-party ID.
+belonging to a user.</li>
+</ul>
+</li>
+<li><code>external_ids</code> - array, optional. Allow setting the identifier of the external identity
+provider for SSO (Single sign-on). Details in
+<a href="admin_api/../usage/configuration/homeserver_sample_config.html">Sample Configuration File</a>
+section <code>sso</code> and <code>oidc_providers</code>.
+<ul>
+<li><code>auth_provider</code> - string. ID of the external identity provider. Value of <code>idp_id</code>
+in the homeserver configuration. Note that no error is raised if the provided
+value is not in the homeserver configuration.</li>
+<li><code>external_id</code> - string, user ID in the external identity provider.</li>
+</ul>
+</li>
+<li><code>avatar_url</code> - string, optional, must be a
+<a href="https://matrix.org/docs/spec/client_server/r0.6.0#matrix-content-mxc-uris">MXC URI</a>.</li>
+<li><code>admin</code> - bool, optional, defaults to <code>false</code>.</li>
+<li><code>deactivated</code> - bool, optional. If unspecified, deactivation state will be left
+unchanged on existing accounts and set to <code>false</code> for new accounts.
+A user cannot be erased by deactivating with this API. For details on
+deactivating users see <a href="admin_api/user_admin_api.html#deactivate-account">Deactivate Account</a>.</li>
+<li><code>user_type</code> - string or null, optional. If provided, the user type will be
+adjusted. If <code>null</code> given, the user type will be cleared. Other 
+allowed options are: <code>bot</code> and <code>support</code>.</li>
+</ul>
+<p>If the user already exists then optional parameters default to the current value.</p>
+<p>In order to re-activate an account <code>deactivated</code> must be set to <code>false</code>. If
+users do not login via single-sign-on, a new <code>password</code> must be provided.</p>
+<h2 id="list-accounts"><a class="header" href="#list-accounts">List Accounts</a></h2>
+<p>This API returns all local user accounts.
+By default, the response is ordered by ascending user ID.</p>
+<pre><code>GET /_synapse/admin/v2/users?from=0&amp;limit=10&amp;guests=false
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;users&quot;: [
+        {
+            &quot;name&quot;: &quot;&lt;user_id1&gt;&quot;,
+            &quot;is_guest&quot;: 0,
+            &quot;admin&quot;: 0,
+            &quot;user_type&quot;: null,
+            &quot;deactivated&quot;: 0,
+            &quot;shadow_banned&quot;: 0,
+            &quot;displayname&quot;: &quot;&lt;User One&gt;&quot;,
+            &quot;avatar_url&quot;: null,
+            &quot;creation_ts&quot;: 1560432668000
+        }, {
+            &quot;name&quot;: &quot;&lt;user_id2&gt;&quot;,
+            &quot;is_guest&quot;: 0,
+            &quot;admin&quot;: 1,
+            &quot;user_type&quot;: null,
+            &quot;deactivated&quot;: 0,
+            &quot;shadow_banned&quot;: 0,
+            &quot;displayname&quot;: &quot;&lt;User Two&gt;&quot;,
+            &quot;avatar_url&quot;: &quot;&lt;avatar_url&gt;&quot;,
+            &quot;creation_ts&quot;: 1561550621000
+        }
+    ],
+    &quot;next_token&quot;: &quot;100&quot;,
+    &quot;total&quot;: 200
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint again
+with <code>from</code> set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more users
+to paginate through.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li>
+<p><code>user_id</code> - Is optional and filters to only return users with user IDs
+that contain this value. This parameter is ignored when using the <code>name</code> parameter.</p>
+</li>
+<li>
+<p><code>name</code> - Is optional and filters to only return users with user ID localparts
+<strong>or</strong> displaynames that contain this value.</p>
+</li>
+<li>
+<p><code>guests</code> - string representing a bool - Is optional and if <code>false</code> will <strong>exclude</strong> guest users.
+Defaults to <code>true</code> to include guest users.</p>
+</li>
+<li>
+<p><code>deactivated</code> - string representing a bool - Is optional and if <code>true</code> will <strong>include</strong> deactivated users.
+Defaults to <code>false</code> to exclude deactivated users.</p>
+</li>
+<li>
+<p><code>limit</code> - string representing a positive integer - Is optional but is used for pagination,
+denoting the maximum number of items to return in this call. Defaults to <code>100</code>.</p>
+</li>
+<li>
+<p><code>from</code> - string representing a positive integer - Is optional but used for pagination,
+denoting the offset in the returned results. This should be treated as an opaque value and
+not explicitly set to anything other than the return value of <code>next_token</code> from a previous call.
+Defaults to <code>0</code>.</p>
+</li>
+<li>
+<p><code>order_by</code> - The method by which to sort the returned list of users.
+If the ordered field has duplicates, the second order is always by ascending <code>name</code>,
+which guarantees a stable ordering. Valid values are:</p>
+<ul>
+<li><code>name</code> - Users are ordered alphabetically by <code>name</code>. This is the default.</li>
+<li><code>is_guest</code> - Users are ordered by <code>is_guest</code> status.</li>
+<li><code>admin</code> - Users are ordered by <code>admin</code> status.</li>
+<li><code>user_type</code> - Users are ordered alphabetically by <code>user_type</code>.</li>
+<li><code>deactivated</code> - Users are ordered by <code>deactivated</code> status.</li>
+<li><code>shadow_banned</code> - Users are ordered by <code>shadow_banned</code> status.</li>
+<li><code>displayname</code> - Users are ordered alphabetically by <code>displayname</code>.</li>
+<li><code>avatar_url</code> - Users are ordered alphabetically by avatar URL.</li>
+<li><code>creation_ts</code> - Users are ordered by when the users was created in ms.</li>
+</ul>
+</li>
+<li>
+<p><code>dir</code> - Direction of media order. Either <code>f</code> for forwards or <code>b</code> for backwards.
+Setting this value to <code>b</code> will reverse the above sort order. Defaults to <code>f</code>.</p>
+</li>
+</ul>
+<p>Caution. The database only has indexes on the columns <code>name</code> and <code>creation_ts</code>.
+This means that if a different sort order is used (<code>is_guest</code>, <code>admin</code>,
+<code>user_type</code>, <code>deactivated</code>, <code>shadow_banned</code>, <code>avatar_url</code> or <code>displayname</code>),
+this can cause a large load on the database, especially for large environments.</p>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li>
+<p><code>users</code> - An array of objects, each containing information about an user.
+User objects contain the following fields:</p>
+<ul>
+<li><code>name</code> - string - Fully-qualified user ID (ex. <code>@user:server.com</code>).</li>
+<li><code>is_guest</code> - bool - Status if that user is a guest account.</li>
+<li><code>admin</code> - bool - Status if that user is a server administrator.</li>
+<li><code>user_type</code> - string - Type of the user. Normal users are type <code>None</code>.
+This allows user type specific behaviour. There are also types <code>support</code> and <code>bot</code>. </li>
+<li><code>deactivated</code> - bool - Status if that user has been marked as deactivated.</li>
+<li><code>shadow_banned</code> - bool - Status if that user has been marked as shadow banned.</li>
+<li><code>displayname</code> - string - The user's display name if they have set one.</li>
+<li><code>avatar_url</code> - string -  The user's avatar URL if they have set one.</li>
+<li><code>creation_ts</code> - integer - The user's creation timestamp in ms.</li>
+</ul>
+</li>
+<li>
+<p><code>next_token</code>: string representing a positive integer - Indication for pagination. See above.</p>
+</li>
+<li>
+<p><code>total</code> - integer - Total number of media.</p>
+</li>
+</ul>
+<h2 id="query-current-sessions-for-a-user"><a class="header" href="#query-current-sessions-for-a-user">Query current sessions for a user</a></h2>
+<p>This API returns information about the active sessions for a specific user.</p>
+<p>The endpoints are:</p>
+<pre><code>GET /_synapse/admin/v1/whois/&lt;user_id&gt;
+</code></pre>
+<p>and:</p>
+<pre><code>GET /_matrix/client/r0/admin/whois/&lt;userId&gt;
+</code></pre>
+<p>See also: <a href="https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid">Client Server
+API Whois</a>.</p>
+<p>It returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+    &quot;user_id&quot;: &quot;&lt;user_id&gt;&quot;,
+    &quot;devices&quot;: {
+        &quot;&quot;: {
+            &quot;sessions&quot;: [
+                {
+                    &quot;connections&quot;: [
+                        {
+                            &quot;ip&quot;: &quot;1.2.3.4&quot;,
+                            &quot;last_seen&quot;: 1417222374433,
+                            &quot;user_agent&quot;: &quot;Mozilla/5.0 ...&quot;
+                        },
+                        {
+                            &quot;ip&quot;: &quot;1.2.3.10&quot;,
+                            &quot;last_seen&quot;: 1417222374500,
+                            &quot;user_agent&quot;: &quot;Dalvik/2.1.0 ...&quot;
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
+</code></pre>
+<p><code>last_seen</code> is measured in milliseconds since the Unix epoch.</p>
+<h2 id="deactivate-account"><a class="header" href="#deactivate-account">Deactivate Account</a></h2>
+<p>This API deactivates an account. It removes active access tokens, resets the
+password, and deletes third-party IDs (to prevent the user requesting a
+password reset).</p>
+<p>It can also mark the user as GDPR-erased. This means messages sent by the
+user will still be visible by anyone that was in the room when these messages
+were sent, but hidden from users joining the room afterwards.</p>
+<p>The api is:</p>
+<pre><code>POST /_synapse/admin/v1/deactivate/&lt;user_id&gt;
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;erase&quot;: true
+}
+</code></pre>
+<p>The erase parameter is optional and defaults to <code>false</code>.
+An empty body may be passed for backwards compatibility.</p>
+<p>The following actions are performed when deactivating an user:</p>
+<ul>
+<li>Try to unbind 3PIDs from the identity server</li>
+<li>Remove all 3PIDs from the homeserver</li>
+<li>Delete all devices and E2EE keys</li>
+<li>Delete all access tokens</li>
+<li>Delete all pushers</li>
+<li>Delete the password hash</li>
+<li>Removal from all rooms the user is a member of</li>
+<li>Remove the user from the user directory</li>
+<li>Reject all pending invites</li>
+<li>Remove all account validity information related to the user</li>
+<li>Remove the arbitrary data store known as <em>account data</em>. For example, this includes:
+<ul>
+<li>list of ignored users;</li>
+<li>push rules;</li>
+<li>secret storage keys; and</li>
+<li>cross-signing keys.</li>
+</ul>
+</li>
+</ul>
+<p>The following additional actions are performed during deactivation if <code>erase</code>
+is set to <code>true</code>:</p>
+<ul>
+<li>Remove the user's display name</li>
+<li>Remove the user's avatar URL</li>
+<li>Mark the user as erased</li>
+</ul>
+<p>The following actions are <strong>NOT</strong> performed. The list may be incomplete.</p>
+<ul>
+<li>Remove mappings of SSO IDs</li>
+<li><a href="admin_api/user_admin_api.html#delete-media-uploaded-by-a-user">Delete media uploaded</a> by user (included avatar images)</li>
+<li>Delete sent and received messages</li>
+<li>Remove the user's creation (registration) timestamp</li>
+<li><a href="admin_api/user_admin_api.html#override-ratelimiting-for-users">Remove rate limit overrides</a></li>
+<li>Remove from monthly active users</li>
+</ul>
+<h2 id="reset-password"><a class="header" href="#reset-password">Reset password</a></h2>
+<p>Changes the password of another user. This will automatically log the user out of all their devices.</p>
+<p>The api is:</p>
+<pre><code>POST /_synapse/admin/v1/reset_password/&lt;user_id&gt;
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+   &quot;new_password&quot;: &quot;&lt;secret&gt;&quot;,
+   &quot;logout_devices&quot;: true
+}
+</code></pre>
+<p>The parameter <code>new_password</code> is required.
+The parameter <code>logout_devices</code> is optional and defaults to <code>true</code>.</p>
+<h2 id="get-whether-a-user-is-a-server-administrator-or-not"><a class="header" href="#get-whether-a-user-is-a-server-administrator-or-not">Get whether a user is a server administrator or not</a></h2>
+<p>The api is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/admin
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;admin&quot;: true
+}
+</code></pre>
+<h2 id="change-whether-a-user-is-a-server-administrator-or-not"><a class="header" href="#change-whether-a-user-is-a-server-administrator-or-not">Change whether a user is a server administrator or not</a></h2>
+<p>Note that you cannot demote yourself.</p>
+<p>The api is:</p>
+<pre><code>PUT /_synapse/admin/v1/users/&lt;user_id&gt;/admin
+</code></pre>
+<p>with a body of:</p>
+<pre><code class="language-json">{
+    &quot;admin&quot;: true
+}
+</code></pre>
+<h2 id="list-room-memberships-of-a-user"><a class="header" href="#list-room-memberships-of-a-user">List room memberships of a user</a></h2>
+<p>Gets a list of all <code>room_id</code> that a specific <code>user_id</code> is member.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/joined_rooms
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">    {
+        &quot;joined_rooms&quot;: [
+            &quot;!DuGcnbhHGaSZQoNQR:matrix.org&quot;,
+            &quot;!ZtSaPCawyWtxfWiIy:matrix.org&quot;
+        ],
+        &quot;total&quot;: 2
+    }
+</code></pre>
+<p>The server returns the list of rooms of which the user and the server
+are member. If the user is local, all the rooms of which the user is
+member are returned.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>joined_rooms</code> - An array of <code>room_id</code>.</li>
+<li><code>total</code> - Number of rooms.</li>
+</ul>
+<h2 id="account-data"><a class="header" href="#account-data">Account Data</a></h2>
+<p>Gets information about account data for a specific <code>user_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/accountdata
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;account_data&quot;: {
+        &quot;global&quot;: {
+            &quot;m.secret_storage.key.LmIGHTg5W&quot;: {
+                &quot;algorithm&quot;: &quot;m.secret_storage.v1.aes-hmac-sha2&quot;,
+                &quot;iv&quot;: &quot;fwjNZatxg==&quot;,
+                &quot;mac&quot;: &quot;eWh9kNnLWZUNOgnc=&quot;
+            },
+            &quot;im.vector.hide_profile&quot;: {
+                &quot;hide_profile&quot;: true
+            },
+            &quot;org.matrix.preview_urls&quot;: {
+                &quot;disable&quot;: false
+            },
+            &quot;im.vector.riot.breadcrumb_rooms&quot;: {
+                &quot;rooms&quot;: [
+                    &quot;!LxcBDAsDUVAfJDEo:matrix.org&quot;,
+                    &quot;!MAhRxqasbItjOqxu:matrix.org&quot;
+                ]
+            },
+            &quot;m.accepted_terms&quot;: {
+                &quot;accepted&quot;: [
+                    &quot;https://example.org/somewhere/privacy-1.2-en.html&quot;,
+                    &quot;https://example.org/somewhere/terms-2.0-en.html&quot;
+                ]
+            },
+            &quot;im.vector.setting.breadcrumbs&quot;: {
+                &quot;recent_rooms&quot;: [
+                    &quot;!MAhRxqasbItqxuEt:matrix.org&quot;,
+                    &quot;!ZtSaPCawyWtxiImy:matrix.org&quot;
+                ]
+            }
+        },
+        &quot;rooms&quot;: {
+            &quot;!GUdfZSHUJibpiVqHYd:matrix.org&quot;: {
+                &quot;m.fully_read&quot;: {
+                    &quot;event_id&quot;: &quot;$156334540fYIhZ:matrix.org&quot;
+                }
+            },
+            &quot;!tOZwOOiqwCYQkLhV:matrix.org&quot;: {
+                &quot;m.fully_read&quot;: {
+                    &quot;event_id&quot;: &quot;$xjsIyp4_NaVl2yPvIZs_k1Jl8tsC_Sp23wjqXPno&quot;
+                }
+            }
+        }
+    }
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>account_data</code> - A map containing the account data for the user
+<ul>
+<li><code>global</code> - A map containing the global account data for the user</li>
+<li><code>rooms</code> - A map containing the account data per room for the user</li>
+</ul>
+</li>
+</ul>
+<h2 id="user-media"><a class="header" href="#user-media">User media</a></h2>
+<h3 id="list-media-uploaded-by-a-user"><a class="header" href="#list-media-uploaded-by-a-user">List media uploaded by a user</a></h3>
+<p>Gets a list of all local media that a specific <code>user_id</code> has created.
+These are media that the user has uploaded themselves
+(<a href="admin_api/../media_repository.html#local-media">local media</a>), as well as
+<a href="admin_api/../media_repository.html#url-previews">URL preview images</a> requested by the user if the
+<a href="admin_api/../development/url_previews.html">feature is enabled</a>.</p>
+<p>By default, the response is ordered by descending creation date and ascending media ID.
+The newest media is on top. You can change the order with parameters
+<code>order_by</code> and <code>dir</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/media
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;media&quot;: [
+    {
+      &quot;created_ts&quot;: 100400,
+      &quot;last_access_ts&quot;: null,
+      &quot;media_id&quot;: &quot;qXhyRzulkwLsNHTbpHreuEgo&quot;,
+      &quot;media_length&quot;: 67,
+      &quot;media_type&quot;: &quot;image/png&quot;,
+      &quot;quarantined_by&quot;: null,
+      &quot;safe_from_quarantine&quot;: false,
+      &quot;upload_name&quot;: &quot;test1.png&quot;
+    },
+    {
+      &quot;created_ts&quot;: 200400,
+      &quot;last_access_ts&quot;: null,
+      &quot;media_id&quot;: &quot;FHfiSnzoINDatrXHQIXBtahw&quot;,
+      &quot;media_length&quot;: 67,
+      &quot;media_type&quot;: &quot;image/png&quot;,
+      &quot;quarantined_by&quot;: null,
+      &quot;safe_from_quarantine&quot;: false,
+      &quot;upload_name&quot;: &quot;test2.png&quot;
+    }
+  ],
+  &quot;next_token&quot;: 3,
+  &quot;total&quot;: 2
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint again
+with <code>from</code> set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more
+reports to paginate through.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li>
+<p><code>user_id</code> - string - fully qualified: for example, <code>@user:server.com</code>.</p>
+</li>
+<li>
+<p><code>limit</code>: string representing a positive integer - Is optional but is used for pagination,
+denoting the maximum number of items to return in this call. Defaults to <code>100</code>.</p>
+</li>
+<li>
+<p><code>from</code>: string representing a positive integer - Is optional but used for pagination,
+denoting the offset in the returned results. This should be treated as an opaque value and
+not explicitly set to anything other than the return value of <code>next_token</code> from a previous call.
+Defaults to <code>0</code>.</p>
+</li>
+<li>
+<p><code>order_by</code> - The method by which to sort the returned list of media.
+If the ordered field has duplicates, the second order is always by ascending <code>media_id</code>,
+which guarantees a stable ordering. Valid values are:</p>
+<ul>
+<li><code>media_id</code> - Media are ordered alphabetically by <code>media_id</code>.</li>
+<li><code>upload_name</code> - Media are ordered alphabetically by name the media was uploaded with.</li>
+<li><code>created_ts</code> - Media are ordered by when the content was uploaded in ms.
+Smallest to largest. This is the default.</li>
+<li><code>last_access_ts</code> - Media are ordered by when the content was last accessed in ms.
+Smallest to largest.</li>
+<li><code>media_length</code> - Media are ordered by length of the media in bytes.
+Smallest to largest.</li>
+<li><code>media_type</code> - Media are ordered alphabetically by MIME-type.</li>
+<li><code>quarantined_by</code> - Media are ordered alphabetically by the user ID that
+initiated the quarantine request for this media.</li>
+<li><code>safe_from_quarantine</code> - Media are ordered by the status if this media is safe
+from quarantining.</li>
+</ul>
+</li>
+<li>
+<p><code>dir</code> - Direction of media order. Either <code>f</code> for forwards or <code>b</code> for backwards.
+Setting this value to <code>b</code> will reverse the above sort order. Defaults to <code>f</code>.</p>
+</li>
+</ul>
+<p>If neither <code>order_by</code> nor <code>dir</code> is set, the default order is newest media on top
+(corresponds to <code>order_by</code> = <code>created_ts</code> and <code>dir</code> = <code>b</code>).</p>
+<p>Caution. The database only has indexes on the columns <code>media_id</code>,
+<code>user_id</code> and <code>created_ts</code>. This means that if a different sort order is used
+(<code>upload_name</code>, <code>last_access_ts</code>, <code>media_length</code>, <code>media_type</code>,
+<code>quarantined_by</code> or <code>safe_from_quarantine</code>), this can cause a large load on the
+database, especially for large environments.</p>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>media</code> - An array of objects, each containing information about a media.
+Media objects contain the following fields:
+<ul>
+<li><code>created_ts</code> - integer - Timestamp when the content was uploaded in ms.</li>
+<li><code>last_access_ts</code> - integer - Timestamp when the content was last accessed in ms.</li>
+<li><code>media_id</code> - string - The id used to refer to the media. Details about the format
+are documented under
+<a href="admin_api/../media_repository.html">media repository</a>.</li>
+<li><code>media_length</code> - integer - Length of the media in bytes.</li>
+<li><code>media_type</code> - string - The MIME-type of the media.</li>
+<li><code>quarantined_by</code> - string - The user ID that initiated the quarantine request
+for this media.</li>
+<li><code>safe_from_quarantine</code> - bool - Status if this media is safe from quarantining.</li>
+<li><code>upload_name</code> - string - The name the media was uploaded with.</li>
+</ul>
+</li>
+<li><code>next_token</code>: integer - Indication for pagination. See above.</li>
+<li><code>total</code> - integer - Total number of media.</li>
+</ul>
+<h3 id="delete-media-uploaded-by-a-user-1"><a class="header" href="#delete-media-uploaded-by-a-user-1">Delete media uploaded by a user</a></h3>
+<p>This API deletes the <em>local</em> media from the disk of your own server
+that a specific <code>user_id</code> has created. This includes any local thumbnails.</p>
+<p>This API will not affect media that has been uploaded to external
+media repositories (e.g https://github.com/turt2live/matrix-media-repo/).</p>
+<p>By default, the API deletes media ordered by descending creation date and ascending media ID.
+The newest media is deleted first. You can change the order with parameters
+<code>order_by</code> and <code>dir</code>. If no <code>limit</code> is set the API deletes <code>100</code> files per request.</p>
+<p>The API is:</p>
+<pre><code>DELETE /_synapse/admin/v1/users/&lt;user_id&gt;/media
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;deleted_media&quot;: [
+    &quot;abcdefghijklmnopqrstuvwx&quot;
+  ],
+  &quot;total&quot;: 1
+}
+</code></pre>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>deleted_media</code>: an array of strings - List of deleted <code>media_id</code></li>
+<li><code>total</code>: integer - Total number of deleted <code>media_id</code></li>
+</ul>
+<p><strong>Note</strong>: There is no <code>next_token</code>. This is not useful for deleting media, because
+after deleting media the remaining media have a new order.</p>
+<p><strong>Parameters</strong></p>
+<p>This API has the same parameters as
+<a href="admin_api/user_admin_api.html#list-media-uploaded-by-a-user">List media uploaded by a user</a>.
+With the parameters you can for example limit the number of files to delete at once or
+delete largest/smallest or newest/oldest files first.</p>
+<h2 id="login-as-a-user"><a class="header" href="#login-as-a-user">Login as a user</a></h2>
+<p>Get an access token that can be used to authenticate as that user. Useful for
+when admins wish to do actions on behalf of a user.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/users/&lt;user_id&gt;/login
+{}
+</code></pre>
+<p>An optional <code>valid_until_ms</code> field can be specified in the request body as an
+integer timestamp that specifies when the token should expire. By default tokens
+do not expire.</p>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+    &quot;access_token&quot;: &quot;&lt;opaque_access_token_string&gt;&quot;
+}
+</code></pre>
+<p>This API does <em>not</em> generate a new device for the user, and so will not appear
+their <code>/devices</code> list, and in general the target user should not be able to
+tell they have been logged in as.</p>
+<p>To expire the token call the standard <code>/logout</code> API with the token.</p>
+<p>Note: The token will expire if the <em>admin</em> user calls <code>/logout/all</code> from any
+of their devices, but the token will <em>not</em> expire if the target user does the
+same.</p>
+<h2 id="user-devices"><a class="header" href="#user-devices">User devices</a></h2>
+<h3 id="list-all-devices"><a class="header" href="#list-all-devices">List all devices</a></h3>
+<p>Gets information about all devices for a specific <code>user_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v2/users/&lt;user_id&gt;/devices
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;devices&quot;: [
+    {
+      &quot;device_id&quot;: &quot;QBUAZIFURK&quot;,
+      &quot;display_name&quot;: &quot;android&quot;,
+      &quot;last_seen_ip&quot;: &quot;1.2.3.4&quot;,
+      &quot;last_seen_ts&quot;: 1474491775024,
+      &quot;user_id&quot;: &quot;&lt;user_id&gt;&quot;
+    },
+    {
+      &quot;device_id&quot;: &quot;AUIECTSRND&quot;,
+      &quot;display_name&quot;: &quot;ios&quot;,
+      &quot;last_seen_ip&quot;: &quot;1.2.3.5&quot;,
+      &quot;last_seen_ts&quot;: 1474491775025,
+      &quot;user_id&quot;: &quot;&lt;user_id&gt;&quot;
+    }
+  ],
+  &quot;total&quot;: 2
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li>
+<p><code>devices</code> - An array of objects, each containing information about a device.
+Device objects contain the following fields:</p>
+<ul>
+<li><code>device_id</code> - Identifier of device.</li>
+<li><code>display_name</code> - Display name set by the user for this device.
+Absent if no name has been set.</li>
+<li><code>last_seen_ip</code> - The IP address where this device was last seen.
+(May be a few minutes out of date, for efficiency reasons).</li>
+<li><code>last_seen_ts</code> - 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).</li>
+<li><code>user_id</code> - Owner of  device.</li>
+</ul>
+</li>
+<li>
+<p><code>total</code> - Total number of user's devices.</p>
+</li>
+</ul>
+<h3 id="delete-multiple-devices"><a class="header" href="#delete-multiple-devices">Delete multiple devices</a></h3>
+<p>Deletes the given devices for a specific <code>user_id</code>, and invalidates
+any access token associated with them.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v2/users/&lt;user_id&gt;/delete_devices
+
+{
+  &quot;devices&quot;: [
+    &quot;QBUAZIFURK&quot;,
+    &quot;AUIECTSRND&quot;
+  ],
+}
+</code></pre>
+<p>An empty JSON dict is returned.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p>The following fields are required in the JSON request body:</p>
+<ul>
+<li><code>devices</code> - The list of device IDs to delete.</li>
+</ul>
+<h3 id="show-a-device"><a class="header" href="#show-a-device">Show a device</a></h3>
+<p>Gets information on a single device, by <code>device_id</code> for a specific <code>user_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v2/users/&lt;user_id&gt;/devices/&lt;device_id&gt;
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;device_id&quot;: &quot;&lt;device_id&gt;&quot;,
+  &quot;display_name&quot;: &quot;android&quot;,
+  &quot;last_seen_ip&quot;: &quot;1.2.3.4&quot;,
+  &quot;last_seen_ts&quot;: 1474491775024,
+  &quot;user_id&quot;: &quot;&lt;user_id&gt;&quot;
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+<li><code>device_id</code> - The device to retrieve.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>device_id</code> - Identifier of device.</li>
+<li><code>display_name</code> - Display name set by the user for this device.
+Absent if no name has been set.</li>
+<li><code>last_seen_ip</code> - The IP address where this device was last seen.
+(May be a few minutes out of date, for efficiency reasons).</li>
+<li><code>last_seen_ts</code> - 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).</li>
+<li><code>user_id</code> - Owner of  device.</li>
+</ul>
+<h3 id="update-a-device"><a class="header" href="#update-a-device">Update a device</a></h3>
+<p>Updates the metadata on the given <code>device_id</code> for a specific <code>user_id</code>.</p>
+<p>The API is:</p>
+<pre><code>PUT /_synapse/admin/v2/users/&lt;user_id&gt;/devices/&lt;device_id&gt;
+
+{
+  &quot;display_name&quot;: &quot;My other phone&quot;
+}
+</code></pre>
+<p>An empty JSON dict is returned.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+<li><code>device_id</code> - The device to update.</li>
+</ul>
+<p>The following fields are required in the JSON request body:</p>
+<ul>
+<li><code>display_name</code> - The new display name for this device. If not given,
+the display name is unchanged.</li>
+</ul>
+<h3 id="delete-a-device"><a class="header" href="#delete-a-device">Delete a device</a></h3>
+<p>Deletes the given <code>device_id</code> for a specific <code>user_id</code>,
+and invalidates any access token associated with it.</p>
+<p>The API is:</p>
+<pre><code>DELETE /_synapse/admin/v2/users/&lt;user_id&gt;/devices/&lt;device_id&gt;
+
+{}
+</code></pre>
+<p>An empty JSON dict is returned.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+<li><code>device_id</code> - The device to delete.</li>
+</ul>
+<h2 id="list-all-pushers"><a class="header" href="#list-all-pushers">List all pushers</a></h2>
+<p>Gets information about all pushers for a specific <code>user_id</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/pushers
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;pushers&quot;: [
+    {
+      &quot;app_display_name&quot;:&quot;HTTP Push Notifications&quot;,
+      &quot;app_id&quot;:&quot;m.http&quot;,
+      &quot;data&quot;: {
+        &quot;url&quot;:&quot;example.com&quot;
+      },
+      &quot;device_display_name&quot;:&quot;pushy push&quot;,
+      &quot;kind&quot;:&quot;http&quot;,
+      &quot;lang&quot;:&quot;None&quot;,
+      &quot;profile_tag&quot;:&quot;&quot;,
+      &quot;pushkey&quot;:&quot;a@example.com&quot;
+    }
+  ],
+  &quot;total&quot;: 1
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - fully qualified: for example, <code>@user:server.com</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li>
+<p><code>pushers</code> - An array containing the current pushers for the user</p>
+<ul>
+<li>
+<p><code>app_display_name</code> - string - A string that will allow the user to identify
+what application owns this pusher.</p>
+</li>
+<li>
+<p><code>app_id</code> - string - This is a reverse-DNS style identifier for the application.
+Max length, 64 chars.</p>
+</li>
+<li>
+<p><code>data</code> - A dictionary of information for the pusher implementation itself.</p>
+<ul>
+<li>
+<p><code>url</code> - string - Required if <code>kind</code> is <code>http</code>. The URL to use to send
+notifications to.</p>
+</li>
+<li>
+<p><code>format</code> - string - The format to use when sending notifications to the
+Push Gateway.</p>
+</li>
+</ul>
+</li>
+<li>
+<p><code>device_display_name</code> - string -  A string that will allow the user to identify
+what device owns this pusher.</p>
+</li>
+<li>
+<p><code>profile_tag</code> - string - This string determines which set of device specific rules
+this pusher executes.</p>
+</li>
+<li>
+<p><code>kind</code> - string -  The kind of pusher. &quot;http&quot; is a pusher that sends HTTP pokes.</p>
+</li>
+<li>
+<p><code>lang</code> - string - The preferred language for receiving notifications
+(e.g. 'en' or 'en-US')</p>
+</li>
+<li>
+<p><code>profile_tag</code> - string - This string determines which set of device specific rules
+this pusher executes.</p>
+</li>
+<li>
+<p><code>pushkey</code> - string - This is a unique identifier for this pusher.
+Max length, 512 bytes.</p>
+</li>
+</ul>
+</li>
+<li>
+<p><code>total</code> - integer - Number of pushers.</p>
+</li>
+</ul>
+<p>See also the
+<a href="https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers">Client-Server API Spec on pushers</a>.</p>
+<h2 id="controlling-whether-a-user-is-shadow-banned"><a class="header" href="#controlling-whether-a-user-is-shadow-banned">Controlling whether a user is shadow-banned</a></h2>
+<p>Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
+A shadow-banned users receives successful responses to their client-server API requests,
+but the events are not propagated into rooms. This can be an effective tool as it
+(hopefully) takes longer for the user to realise they are being moderated before
+pivoting to another account.</p>
+<p>Shadow-banning a user should be used as a tool of last resort and may lead to confusing
+or broken behaviour for the client. A shadow-banned user will not receive any
+notification and it is generally more appropriate to ban or kick abusive users.
+A shadow-banned user will be unable to contact anyone on the server.</p>
+<p>To shadow-ban a user the API is:</p>
+<pre><code>POST /_synapse/admin/v1/users/&lt;user_id&gt;/shadow_ban
+</code></pre>
+<p>To un-shadow-ban a user the API is:</p>
+<pre><code>DELETE /_synapse/admin/v1/users/&lt;user_id&gt;/shadow_ban
+</code></pre>
+<p>An empty JSON dict is returned in both cases.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - The fully qualified MXID: for example, <code>@user:server.com</code>. The user must
+be local.</li>
+</ul>
+<h2 id="override-ratelimiting-for-users"><a class="header" href="#override-ratelimiting-for-users">Override ratelimiting for users</a></h2>
+<p>This API allows to override or disable ratelimiting for a specific user.
+There are specific APIs to set, get and delete a ratelimit.</p>
+<h3 id="get-status-of-ratelimit"><a class="header" href="#get-status-of-ratelimit">Get status of ratelimit</a></h3>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/users/&lt;user_id&gt;/override_ratelimit
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;messages_per_second&quot;: 0,
+  &quot;burst_count&quot;: 0
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - The fully qualified MXID: for example, <code>@user:server.com</code>. The user must
+be local.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>messages_per_second</code> - integer - The number of actions that can
+be performed in a second. <code>0</code> mean that ratelimiting is disabled for this user.</li>
+<li><code>burst_count</code> - integer - How many actions that can be performed before
+being limited.</li>
+</ul>
+<p>If <strong>no</strong> custom ratelimit is set, an empty JSON dict is returned.</p>
+<pre><code class="language-json">{}
+</code></pre>
+<h3 id="set-ratelimit"><a class="header" href="#set-ratelimit">Set ratelimit</a></h3>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/users/&lt;user_id&gt;/override_ratelimit
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+  &quot;messages_per_second&quot;: 0,
+  &quot;burst_count&quot;: 0
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - The fully qualified MXID: for example, <code>@user:server.com</code>. The user must
+be local.</li>
+</ul>
+<p>Body parameters:</p>
+<ul>
+<li><code>messages_per_second</code> - positive integer, optional. The number of actions that can
+be performed in a second. Defaults to <code>0</code>.</li>
+<li><code>burst_count</code> - positive integer, optional. How many actions that can be performed
+before being limited. Defaults to <code>0</code>.</li>
+</ul>
+<p>To disable users' ratelimit set both values to <code>0</code>.</p>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>messages_per_second</code> - integer - The number of actions that can
+be performed in a second.</li>
+<li><code>burst_count</code> - integer - How many actions that can be performed before
+being limited.</li>
+</ul>
+<h3 id="delete-ratelimit"><a class="header" href="#delete-ratelimit">Delete ratelimit</a></h3>
+<p>The API is:</p>
+<pre><code>DELETE /_synapse/admin/v1/users/&lt;user_id&gt;/override_ratelimit
+</code></pre>
+<p>An empty JSON dict is returned.</p>
+<pre><code class="language-json">{}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>user_id</code> - The fully qualified MXID: for example, <code>@user:server.com</code>. The user must
+be local.</li>
+</ul>
+<h3 id="check-username-availability"><a class="header" href="#check-username-availability">Check username availability</a></h3>
+<p>Checks to see if a username is available, and valid, for the server. See <a href="https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-register-available">the client-server 
+API</a>
+for more information.</p>
+<p>This endpoint will work even if registration is disabled on the server, unlike 
+<code>/_matrix/client/r0/register/available</code>.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/username_available?username=$localpart
+</code></pre>
+<p>The request and response format is the same as the
+<a href="https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-register-available">/_matrix/client/r0/register/available</a> API.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="version-api"><a class="header" href="#version-api">Version API</a></h1>
+<p>This API returns the running Synapse version and the Python version
+on which Synapse is being run. This is useful when a Synapse instance
+is behind a proxy that does not forward the 'Server' header (which also
+contains Synapse version information).</p>
+<p>The api is:</p>
+<pre><code>GET /_synapse/admin/v1/server_version
+</code></pre>
+<p>It returns a JSON body like the following:</p>
+<pre><code class="language-json">{
+    &quot;server_version&quot;: &quot;0.99.2rc1 (b=develop, abcdef123)&quot;,
+    &quot;python_version&quot;: &quot;3.7.8&quot;
+}
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="federation-api"><a class="header" href="#federation-api">Federation API</a></h1>
+<p>This API allows a server administrator to manage Synapse's federation with other homeservers.</p>
+<p>Note: This API is new, experimental and &quot;subject to change&quot;.</p>
+<h2 id="list-of-destinations"><a class="header" href="#list-of-destinations">List of destinations</a></h2>
+<p>This API gets the current destination retry timing info for all remote servers.</p>
+<p>The list contains all the servers with which the server federates,
+regardless of whether an error occurred or not.
+If an error occurs, it may take up to 20 minutes for the error to be displayed here,
+as a complete retry must have failed.</p>
+<p>The API is:</p>
+<p>A standard request with no filtering:</p>
+<pre><code>GET /_synapse/admin/v1/federation/destinations
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+   &quot;destinations&quot;:[
+      {
+         &quot;destination&quot;: &quot;matrix.org&quot;,
+         &quot;retry_last_ts&quot;: 1557332397936,
+         &quot;retry_interval&quot;: 3000000,
+         &quot;failure_ts&quot;: 1557329397936,
+         &quot;last_successful_stream_ordering&quot;: null
+      }
+   ],
+   &quot;total&quot;: 1
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint again
+with <code>from</code> set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more destinations
+to paginate through.</p>
+<p><strong>Parameters</strong></p>
+<p>The following query parameters are available:</p>
+<ul>
+<li><code>from</code> - Offset in the returned list. Defaults to <code>0</code>.</li>
+<li><code>limit</code> - Maximum amount of destinations to return. Defaults to <code>100</code>.</li>
+<li><code>order_by</code> - The method in which to sort the returned list of destinations.
+Valid values are:
+<ul>
+<li><code>destination</code> - Destinations are ordered alphabetically by remote server name.
+This is the default.</li>
+<li><code>retry_last_ts</code> - Destinations are ordered by time of last retry attempt in ms.</li>
+<li><code>retry_interval</code> - Destinations are ordered by how long until next retry in ms.</li>
+<li><code>failure_ts</code> - Destinations are ordered by when the server started failing in ms.</li>
+<li><code>last_successful_stream_ordering</code> - Destinations are ordered by the stream ordering
+of the most recent successfully-sent PDU.</li>
+</ul>
+</li>
+<li><code>dir</code> - Direction of room order. Either <code>f</code> for forwards or <code>b</code> for backwards. Setting
+this value to <code>b</code> will reverse the above sort order. Defaults to <code>f</code>.</li>
+</ul>
+<p><em>Caution:</em> The database only has an index on the column <code>destination</code>.
+This means that if a different sort order is used,
+this can cause a large load on the database, especially for large environments.</p>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>destinations</code> - An array of objects, each containing information about a destination.
+Destination objects contain the following fields:
+<ul>
+<li><code>destination</code> - string - Name of the remote server to federate.</li>
+<li><code>retry_last_ts</code> - integer - The last time Synapse tried and failed to reach the
+remote server, in ms. This is <code>0</code> if the last attempt to communicate with the
+remote server was successful.</li>
+<li><code>retry_interval</code> - integer - How long since the last time Synapse tried to reach
+the remote server before trying again, in ms. This is <code>0</code> if no further retrying occuring.</li>
+<li><code>failure_ts</code> - nullable integer - The first time Synapse tried and failed to reach the
+remote server, in ms. This is <code>null</code> if communication with the remote server has never failed.</li>
+<li><code>last_successful_stream_ordering</code> - nullable integer - The stream ordering of the most
+recent successfully-sent <a href="usage/administration/admin_api/understanding_synapse_through_grafana_graphs.html#federation">PDU</a>
+to this destination, or <code>null</code> if this information has not been tracked yet.</li>
+</ul>
+</li>
+<li><code>next_token</code>: string representing a positive integer - Indication for pagination. See above.</li>
+<li><code>total</code> - integer - Total number of destinations.</li>
+</ul>
+<h2 id="destination-details-api"><a class="header" href="#destination-details-api">Destination Details API</a></h2>
+<p>This API gets the retry timing info for a specific remote server.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/federation/destinations/&lt;destination&gt;
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+   &quot;destination&quot;: &quot;matrix.org&quot;,
+   &quot;retry_last_ts&quot;: 1557332397936,
+   &quot;retry_interval&quot;: 3000000,
+   &quot;failure_ts&quot;: 1557329397936,
+   &quot;last_successful_stream_ordering&quot;: null
+}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>destination</code> - Name of the remote server.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The response fields are the same like in the <code>destinations</code> array in
+<a href="usage/administration/admin_api/federation.html#list-of-destinations">List of destinations</a> response.</p>
+<h2 id="destination-rooms"><a class="header" href="#destination-rooms">Destination rooms</a></h2>
+<p>This API gets the rooms that federate with a specific remote server.</p>
+<p>The API is:</p>
+<pre><code>GET /_synapse/admin/v1/federation/destinations/&lt;destination&gt;/rooms
+</code></pre>
+<p>A response body like the following is returned:</p>
+<pre><code class="language-json">{
+   &quot;rooms&quot;:[
+      {
+         &quot;room_id&quot;: &quot;!OGEhHVWSdvArJzumhm:matrix.org&quot;,
+         &quot;stream_ordering&quot;: 8326
+      },
+      {
+         &quot;room_id&quot;: &quot;!xYvNcQPhnkrdUmYczI:matrix.org&quot;,
+         &quot;stream_ordering&quot;: 93534
+      }
+   ],
+   &quot;total&quot;: 2
+}
+</code></pre>
+<p>To paginate, check for <code>next_token</code> and if present, call the endpoint again
+with <code>from</code> set to the value of <code>next_token</code>. This will return a new page.</p>
+<p>If the endpoint does not return a <code>next_token</code> then there are no more destinations
+to paginate through.</p>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>destination</code> - Name of the remote server.</li>
+</ul>
+<p>The following query parameters are available:</p>
+<ul>
+<li><code>from</code> - Offset in the returned list. Defaults to <code>0</code>.</li>
+<li><code>limit</code> - Maximum amount of destinations to return. Defaults to <code>100</code>.</li>
+<li><code>dir</code> - Direction of room order by <code>room_id</code>. Either <code>f</code> for forwards or <code>b</code> for
+backwards. Defaults to <code>f</code>.</li>
+</ul>
+<p><strong>Response</strong></p>
+<p>The following fields are returned in the JSON response body:</p>
+<ul>
+<li><code>rooms</code> - An array of objects, each containing information about a room.
+Room objects contain the following fields:
+<ul>
+<li><code>room_id</code> - string - The ID of the room.</li>
+<li><code>stream_ordering</code> - integer -  The stream ordering of the most recent
+successfully-sent <a href="usage/administration/admin_api/understanding_synapse_through_grafana_graphs.html#federation">PDU</a>
+to this destination in this room.</li>
+</ul>
+</li>
+<li><code>next_token</code>: string representing a positive integer - Indication for pagination. See above.</li>
+<li><code>total</code> - integer - Total number of destinations.</li>
+</ul>
+<h2 id="reset-connection-timeout"><a class="header" href="#reset-connection-timeout">Reset connection timeout</a></h2>
+<p>Synapse makes federation requests to other homeservers. If a federation request fails,
+Synapse will mark the destination homeserver as offline, preventing any future requests
+to that server for a &quot;cooldown&quot; period. This period grows over time if the server
+continues to fail its responses
+(<a href="https://en.wikipedia.org/wiki/Exponential_backoff">exponential backoff</a>).</p>
+<p>Admins can cancel the cooldown period with this API.</p>
+<p>This API resets the retry timing for a specific remote server and tries to connect to
+the remote server again. It does not wait for the next <code>retry_interval</code>.
+The connection must have previously run into an error and <code>retry_last_ts</code>
+(<a href="usage/administration/admin_api/federation.html#destination-details-api">Destination Details API</a>) must not be equal to <code>0</code>.</p>
+<p>The connection attempt is carried out in the background and can take a while
+even if the API already returns the http status 200.</p>
+<p>The API is:</p>
+<pre><code>POST /_synapse/admin/v1/federation/destinations/&lt;destination&gt;/reset_connection
+
+{}
+</code></pre>
+<p><strong>Parameters</strong></p>
+<p>The following parameters should be set in the URL:</p>
+<ul>
+<li><code>destination</code> - Name of the remote server.</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="using-the-synapse-manhole"><a class="header" href="#using-the-synapse-manhole">Using the synapse manhole</a></h1>
+<p>The &quot;manhole&quot; allows server administrators to access a Python shell on a running
+Synapse installation. This is a very powerful mechanism for administration and
+debugging.</p>
+<p><strong><em>Security Warning</em></strong></p>
+<p>Note that this will give administrative access to synapse to <strong>all users</strong> with
+shell access to the server. It should therefore <strong>not</strong> be enabled in
+environments where untrusted users have shell access.</p>
+<h2 id="configuring-the-manhole"><a class="header" href="#configuring-the-manhole">Configuring the manhole</a></h2>
+<p>To enable it, first uncomment the <code>manhole</code> listener configuration in
+<code>homeserver.yaml</code>. The configuration is slightly different if you're using docker.</p>
+<h4 id="docker-config"><a class="header" href="#docker-config">Docker config</a></h4>
+<p>If you are using Docker, set <code>bind_addresses</code> to <code>['0.0.0.0']</code> as shown:</p>
+<pre><code class="language-yaml">listeners:
+  - port: 9000
+    bind_addresses: ['0.0.0.0']
+    type: manhole
+</code></pre>
+<p>When using <code>docker run</code> to start the server, you will then need to change the command to the following to include the
+<code>manhole</code> port forwarding. The <code>-p 127.0.0.1:9000:9000</code> below is important: it 
+ensures that access to the <code>manhole</code> is only possible for local users.</p>
+<pre><code class="language-bash">docker run -d --name synapse \
+    --mount type=volume,src=synapse-data,dst=/data \
+    -p 8008:8008 \
+    -p 127.0.0.1:9000:9000 \
+    matrixdotorg/synapse:latest
+</code></pre>
+<h4 id="native-config"><a class="header" href="#native-config">Native config</a></h4>
+<p>If you are not using docker, set <code>bind_addresses</code> to <code>['::1', '127.0.0.1']</code> as shown.
+The <code>bind_addresses</code> in the example below is important: it ensures that access to the
+<code>manhole</code> is only possible for local users).</p>
+<pre><code class="language-yaml">listeners:
+  - port: 9000
+    bind_addresses: ['::1', '127.0.0.1']
+    type: manhole
+</code></pre>
+<h3 id="security-settings"><a class="header" href="#security-settings">Security settings</a></h3>
+<p>The following config options are available:</p>
+<ul>
+<li><code>username</code> - The username for the manhole (defaults to <code>matrix</code>)</li>
+<li><code>password</code> - The password for the manhole (defaults to <code>rabbithole</code>)</li>
+<li><code>ssh_priv_key</code> - The path to a private SSH key (defaults to a hardcoded value)</li>
+<li><code>ssh_pub_key</code> - The path to a public SSH key (defaults to a hardcoded value)</li>
+</ul>
+<p>For example:</p>
+<pre><code class="language-yaml">manhole_settings:
+  username: manhole
+  password: mypassword
+  ssh_priv_key: &quot;/home/synapse/manhole_keys/id_rsa&quot;
+  ssh_pub_key: &quot;/home/synapse/manhole_keys/id_rsa.pub&quot;
+</code></pre>
+<h2 id="accessing-synapse-manhole"><a class="header" href="#accessing-synapse-manhole">Accessing synapse manhole</a></h2>
+<p>Then restart synapse, and point an ssh client at port 9000 on localhost, using
+the username and password configured in <code>homeserver.yaml</code> - with the default 
+configuration, this would be:</p>
+<pre><code class="language-bash">ssh -p9000 matrix@localhost
+</code></pre>
+<p>Then enter the password when prompted (the default is <code>rabbithole</code>).</p>
+<p>This gives a Python REPL in which <code>hs</code> gives access to the
+<code>synapse.server.HomeServer</code> object - which in turn gives access to many other
+parts of the process.</p>
+<p>Note that, prior to Synapse 1.41, any call which returns a coroutine will need to be wrapped in <code>ensureDeferred</code>.</p>
+<p>As a simple example, retrieving an event from the database:</p>
+<pre><code class="language-pycon">&gt;&gt;&gt; from twisted.internet import defer
+&gt;&gt;&gt; defer.ensureDeferred(hs.get_datastores().main.get_event('$1416420717069yeQaw:matrix.org'))
+&lt;Deferred at 0x7ff253fc6998 current result: &lt;FrozenEvent event_id='$1416420717069yeQaw:matrix.org', type='m.room.create', state_key=''&gt;&gt;
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="how-to-monitor-synapse-metrics-using-prometheus"><a class="header" href="#how-to-monitor-synapse-metrics-using-prometheus">How to monitor Synapse metrics using Prometheus</a></h1>
+<ol>
+<li>
+<p>Install Prometheus:</p>
+<p>Follow instructions at
+<a href="http://prometheus.io/docs/introduction/install/">http://prometheus.io/docs/introduction/install/</a></p>
+</li>
+<li>
+<p>Enable Synapse metrics:</p>
+<p>There are two methods of enabling metrics in Synapse.</p>
+<p>The first serves the metrics as a part of the usual web server and
+can be enabled by adding the &quot;metrics&quot; resource to the existing
+listener as such:</p>
+<pre><code class="language-yaml">  resources:
+    - names:
+      - client
+      - metrics
+</code></pre>
+<p>This provides a simple way of adding metrics to your Synapse
+installation, and serves under <code>/_synapse/metrics</code>. If you do not
+wish your metrics be publicly exposed, you will need to either
+filter it out at your load balancer, or use the second method.</p>
+<p>The second method runs the metrics server on a different port, in a
+different thread to Synapse. This can make it more resilient to
+heavy load meaning metrics cannot be retrieved, and can be exposed
+to just internal networks easier. The served metrics are available
+over HTTP only, and will be available at <code>/_synapse/metrics</code>.</p>
+<p>Add a new listener to homeserver.yaml:</p>
+<pre><code class="language-yaml">  listeners:
+    - type: metrics
+      port: 9000
+      bind_addresses:
+        - '0.0.0.0'
+</code></pre>
+<p>For both options, you will need to ensure that <code>enable_metrics</code> is
+set to <code>True</code>.</p>
+</li>
+<li>
+<p>Restart Synapse.</p>
+</li>
+<li>
+<p>Add a Prometheus target for Synapse.</p>
+<p>It needs to set the <code>metrics_path</code> to a non-default value (under
+<code>scrape_configs</code>):</p>
+<pre><code class="language-yaml">  - job_name: &quot;synapse&quot;
+    scrape_interval: 15s
+    metrics_path: &quot;/_synapse/metrics&quot;
+    static_configs:
+      - targets: [&quot;my.server.here:port&quot;]
+</code></pre>
+<p>where <code>my.server.here</code> is the IP address of Synapse, and <code>port</code> is
+the listener port configured with the <code>metrics</code> resource.</p>
+<p>If your prometheus is older than 1.5.2, you will need to replace
+<code>static_configs</code> in the above with <code>target_groups</code>.</p>
+</li>
+<li>
+<p>Restart Prometheus.</p>
+</li>
+<li>
+<p>Consider using the <a href="https://github.com/matrix-org/synapse/tree/master/contrib/grafana/">grafana dashboard</a>
+and required <a href="https://github.com/matrix-org/synapse/tree/master/contrib/prometheus/">recording rules</a> </p>
+</li>
+</ol>
+<h2 id="monitoring-workers"><a class="header" href="#monitoring-workers">Monitoring workers</a></h2>
+<p>To monitor a Synapse installation using <a href="workers.html">workers</a>,
+every worker needs to be monitored independently, in addition to
+the main homeserver process. This is because workers don't send
+their metrics to the main homeserver process, but expose them
+directly (if they are configured to do so).</p>
+<p>To allow collecting metrics from a worker, you need to add a
+<code>metrics</code> listener to its configuration, by adding the following
+under <code>worker_listeners</code>:</p>
+<pre><code class="language-yaml">  - type: metrics
+    bind_address: ''
+    port: 9101
+</code></pre>
+<p>The <code>bind_address</code> and <code>port</code> parameters should be set so that
+the resulting listener can be reached by prometheus, and they
+don't clash with an existing worker.
+With this example, the worker's metrics would then be available
+on <code>http://127.0.0.1:9101</code>.</p>
+<p>Example Prometheus target for Synapse with workers:</p>
+<pre><code class="language-yaml">  - job_name: &quot;synapse&quot;
+    scrape_interval: 15s
+    metrics_path: &quot;/_synapse/metrics&quot;
+    static_configs:
+      - targets: [&quot;my.server.here:port&quot;]
+        labels:
+          instance: &quot;my.server&quot;
+          job: &quot;master&quot;
+          index: 1
+      - targets: [&quot;my.workerserver.here:port&quot;]
+        labels:
+          instance: &quot;my.server&quot;
+          job: &quot;generic_worker&quot;
+          index: 1
+      - targets: [&quot;my.workerserver.here:port&quot;]
+        labels:
+          instance: &quot;my.server&quot;
+          job: &quot;generic_worker&quot;
+          index: 2
+      - targets: [&quot;my.workerserver.here:port&quot;]
+        labels:
+          instance: &quot;my.server&quot;
+          job: &quot;media_repository&quot;
+          index: 1
+</code></pre>
+<p>Labels (<code>instance</code>, <code>job</code>, <code>index</code>) can be defined as anything.
+The labels are used to group graphs in grafana.</p>
+<h2 id="renaming-of-metrics--deprecation-of-old-names-in-12"><a class="header" href="#renaming-of-metrics--deprecation-of-old-names-in-12">Renaming of metrics &amp; deprecation of old names in 1.2</a></h2>
+<p>Synapse 1.2 updates the Prometheus metrics to match the naming
+convention of the upstream <code>prometheus_client</code>. The old names are
+considered deprecated and will be removed in a future version of
+Synapse.</p>
+<table><thead><tr><th>New Name</th><th>Old Name</th></tr></thead><tbody>
+<tr><td>python_gc_objects_collected_total</td><td>python_gc_objects_collected</td></tr>
+<tr><td>python_gc_objects_uncollectable_total</td><td>python_gc_objects_uncollectable</td></tr>
+<tr><td>python_gc_collections_total</td><td>python_gc_collections</td></tr>
+<tr><td>process_cpu_seconds_total</td><td>process_cpu_seconds</td></tr>
+<tr><td>synapse_federation_client_sent_transactions_total</td><td>synapse_federation_client_sent_transactions</td></tr>
+<tr><td>synapse_federation_client_events_processed_total</td><td>synapse_federation_client_events_processed</td></tr>
+<tr><td>synapse_event_processing_loop_count_total</td><td>synapse_event_processing_loop_count</td></tr>
+<tr><td>synapse_event_processing_loop_room_count_total</td><td>synapse_event_processing_loop_room_count</td></tr>
+<tr><td>synapse_util_metrics_block_count_total</td><td>synapse_util_metrics_block_count</td></tr>
+<tr><td>synapse_util_metrics_block_time_seconds_total</td><td>synapse_util_metrics_block_time_seconds</td></tr>
+<tr><td>synapse_util_metrics_block_ru_utime_seconds_total</td><td>synapse_util_metrics_block_ru_utime_seconds</td></tr>
+<tr><td>synapse_util_metrics_block_ru_stime_seconds_total</td><td>synapse_util_metrics_block_ru_stime_seconds</td></tr>
+<tr><td>synapse_util_metrics_block_db_txn_count_total</td><td>synapse_util_metrics_block_db_txn_count</td></tr>
+<tr><td>synapse_util_metrics_block_db_txn_duration_seconds_total</td><td>synapse_util_metrics_block_db_txn_duration_seconds</td></tr>
+<tr><td>synapse_util_metrics_block_db_sched_duration_seconds_total</td><td>synapse_util_metrics_block_db_sched_duration_seconds</td></tr>
+<tr><td>synapse_background_process_start_count_total</td><td>synapse_background_process_start_count</td></tr>
+<tr><td>synapse_background_process_ru_utime_seconds_total</td><td>synapse_background_process_ru_utime_seconds</td></tr>
+<tr><td>synapse_background_process_ru_stime_seconds_total</td><td>synapse_background_process_ru_stime_seconds</td></tr>
+<tr><td>synapse_background_process_db_txn_count_total</td><td>synapse_background_process_db_txn_count</td></tr>
+<tr><td>synapse_background_process_db_txn_duration_seconds_total</td><td>synapse_background_process_db_txn_duration_seconds</td></tr>
+<tr><td>synapse_background_process_db_sched_duration_seconds_total</td><td>synapse_background_process_db_sched_duration_seconds</td></tr>
+<tr><td>synapse_storage_events_persisted_events_total</td><td>synapse_storage_events_persisted_events</td></tr>
+<tr><td>synapse_storage_events_persisted_events_sep_total</td><td>synapse_storage_events_persisted_events_sep</td></tr>
+<tr><td>synapse_storage_events_state_delta_total</td><td>synapse_storage_events_state_delta</td></tr>
+<tr><td>synapse_storage_events_state_delta_single_event_total</td><td>synapse_storage_events_state_delta_single_event</td></tr>
+<tr><td>synapse_storage_events_state_delta_reuse_delta_total</td><td>synapse_storage_events_state_delta_reuse_delta</td></tr>
+<tr><td>synapse_federation_server_received_pdus_total</td><td>synapse_federation_server_received_pdus</td></tr>
+<tr><td>synapse_federation_server_received_edus_total</td><td>synapse_federation_server_received_edus</td></tr>
+<tr><td>synapse_handler_presence_notified_presence_total</td><td>synapse_handler_presence_notified_presence</td></tr>
+<tr><td>synapse_handler_presence_federation_presence_out_total</td><td>synapse_handler_presence_federation_presence_out</td></tr>
+<tr><td>synapse_handler_presence_presence_updates_total</td><td>synapse_handler_presence_presence_updates</td></tr>
+<tr><td>synapse_handler_presence_timers_fired_total</td><td>synapse_handler_presence_timers_fired</td></tr>
+<tr><td>synapse_handler_presence_federation_presence_total</td><td>synapse_handler_presence_federation_presence</td></tr>
+<tr><td>synapse_handler_presence_bump_active_time_total</td><td>synapse_handler_presence_bump_active_time</td></tr>
+<tr><td>synapse_federation_client_sent_edus_total</td><td>synapse_federation_client_sent_edus</td></tr>
+<tr><td>synapse_federation_client_sent_pdu_destinations_count_total</td><td>synapse_federation_client_sent_pdu_destinations:count</td></tr>
+<tr><td>synapse_federation_client_sent_pdu_destinations_total</td><td>synapse_federation_client_sent_pdu_destinations:total</td></tr>
+<tr><td>synapse_handlers_appservice_events_processed_total</td><td>synapse_handlers_appservice_events_processed</td></tr>
+<tr><td>synapse_notifier_notified_events_total</td><td>synapse_notifier_notified_events</td></tr>
+<tr><td>synapse_push_bulk_push_rule_evaluator_push_rules_invalidation_counter_total</td><td>synapse_push_bulk_push_rule_evaluator_push_rules_invalidation_counter</td></tr>
+<tr><td>synapse_push_bulk_push_rule_evaluator_push_rules_state_size_counter_total</td><td>synapse_push_bulk_push_rule_evaluator_push_rules_state_size_counter</td></tr>
+<tr><td>synapse_http_httppusher_http_pushes_processed_total</td><td>synapse_http_httppusher_http_pushes_processed</td></tr>
+<tr><td>synapse_http_httppusher_http_pushes_failed_total</td><td>synapse_http_httppusher_http_pushes_failed</td></tr>
+<tr><td>synapse_http_httppusher_badge_updates_processed_total</td><td>synapse_http_httppusher_badge_updates_processed</td></tr>
+<tr><td>synapse_http_httppusher_badge_updates_failed_total</td><td>synapse_http_httppusher_badge_updates_failed</td></tr>
+</tbody></table>
+<h2 id="removal-of-deprecated-metrics--time-based-counters-becoming-histograms-in-0310"><a class="header" href="#removal-of-deprecated-metrics--time-based-counters-becoming-histograms-in-0310">Removal of deprecated metrics &amp; time based counters becoming histograms in 0.31.0</a></h2>
+<p>The duplicated metrics deprecated in Synapse 0.27.0 have been removed.</p>
+<p>All time duration-based metrics have been changed to be seconds. This
+affects:</p>
+<table><thead><tr><th>msec -&gt; sec metrics</th></tr></thead><tbody>
+<tr><td>python_gc_time</td></tr>
+<tr><td>python_twisted_reactor_tick_time</td></tr>
+<tr><td>synapse_storage_query_time</td></tr>
+<tr><td>synapse_storage_schedule_time</td></tr>
+<tr><td>synapse_storage_transaction_time</td></tr>
+</tbody></table>
+<p>Several metrics have been changed to be histograms, which sort entries
+into buckets and allow better analysis. The following metrics are now
+histograms:</p>
+<table><thead><tr><th>Altered metrics</th></tr></thead><tbody>
+<tr><td>python_gc_time</td></tr>
+<tr><td>python_twisted_reactor_pending_calls</td></tr>
+<tr><td>python_twisted_reactor_tick_time</td></tr>
+<tr><td>synapse_http_server_response_time_seconds</td></tr>
+<tr><td>synapse_storage_query_time</td></tr>
+<tr><td>synapse_storage_schedule_time</td></tr>
+<tr><td>synapse_storage_transaction_time</td></tr>
+</tbody></table>
+<h2 id="block-and-response-metrics-renamed-for-0270"><a class="header" href="#block-and-response-metrics-renamed-for-0270">Block and response metrics renamed for 0.27.0</a></h2>
+<p>Synapse 0.27.0 begins the process of rationalising the duplicate
+<code>*:count</code> metrics reported for the resource tracking for code blocks and
+HTTP requests.</p>
+<p>At the same time, the corresponding <code>*:total</code> metrics are being renamed,
+as the <code>:total</code> suffix no longer makes sense in the absence of a
+corresponding <code>:count</code> metric.</p>
+<p>To enable a graceful migration path, this release just adds new names
+for the metrics being renamed. A future release will remove the old
+ones.</p>
+<p>The following table shows the new metrics, and the old metrics which
+they are replacing.</p>
+<table><thead><tr><th>New name</th><th>Old name</th></tr></thead><tbody>
+<tr><td>synapse_util_metrics_block_count</td><td>synapse_util_metrics_block_timer:count</td></tr>
+<tr><td>synapse_util_metrics_block_count</td><td>synapse_util_metrics_block_ru_utime:count</td></tr>
+<tr><td>synapse_util_metrics_block_count</td><td>synapse_util_metrics_block_ru_stime:count</td></tr>
+<tr><td>synapse_util_metrics_block_count</td><td>synapse_util_metrics_block_db_txn_count:count</td></tr>
+<tr><td>synapse_util_metrics_block_count</td><td>synapse_util_metrics_block_db_txn_duration:count</td></tr>
+<tr><td>synapse_util_metrics_block_time_seconds</td><td>synapse_util_metrics_block_timer:total</td></tr>
+<tr><td>synapse_util_metrics_block_ru_utime_seconds</td><td>synapse_util_metrics_block_ru_utime:total</td></tr>
+<tr><td>synapse_util_metrics_block_ru_stime_seconds</td><td>synapse_util_metrics_block_ru_stime:total</td></tr>
+<tr><td>synapse_util_metrics_block_db_txn_count</td><td>synapse_util_metrics_block_db_txn_count:total</td></tr>
+<tr><td>synapse_util_metrics_block_db_txn_duration_seconds</td><td>synapse_util_metrics_block_db_txn_duration:total</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_requests</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_response_time:count</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_response_ru_utime:count</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_response_ru_stime:count</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_response_db_txn_count:count</td></tr>
+<tr><td>synapse_http_server_response_count</td><td>synapse_http_server_response_db_txn_duration:count</td></tr>
+<tr><td>synapse_http_server_response_time_seconds</td><td>synapse_http_server_response_time:total</td></tr>
+<tr><td>synapse_http_server_response_ru_utime_seconds</td><td>synapse_http_server_response_ru_utime:total</td></tr>
+<tr><td>synapse_http_server_response_ru_stime_seconds</td><td>synapse_http_server_response_ru_stime:total</td></tr>
+<tr><td>synapse_http_server_response_db_txn_count</td><td>synapse_http_server_response_db_txn_count:total</td></tr>
+<tr><td>synapse_http_server_response_db_txn_duration_seconds</td><td>synapse_http_server_response_db_txn_duration:total</td></tr>
+</tbody></table>
+<h2 id="standard-metric-names"><a class="header" href="#standard-metric-names">Standard Metric Names</a></h2>
+<p>As of synapse version 0.18.2, the format of the process-wide metrics has
+been changed to fit prometheus standard naming conventions. Additionally
+the units have been changed to seconds, from miliseconds.</p>
+<table><thead><tr><th>New name</th><th>Old name</th></tr></thead><tbody>
+<tr><td>process_cpu_user_seconds_total</td><td>process_resource_utime / 1000</td></tr>
+<tr><td>process_cpu_system_seconds_total</td><td>process_resource_stime / 1000</td></tr>
+<tr><td>process_open_fds (no 'type' label)</td><td>process_fds</td></tr>
+</tbody></table>
+<p>The python-specific counts of garbage collector performance have been
+renamed.</p>
+<table><thead><tr><th>New name</th><th>Old name</th></tr></thead><tbody>
+<tr><td>python_gc_time</td><td>reactor_gc_time</td></tr>
+<tr><td>python_gc_unreachable_total</td><td>reactor_gc_unreachable</td></tr>
+<tr><td>python_gc_counts</td><td>reactor_gc_counts</td></tr>
+</tbody></table>
+<p>The twisted-specific reactor metrics have been renamed.</p>
+<table><thead><tr><th>New name</th><th>Old name</th></tr></thead><tbody>
+<tr><td>python_twisted_reactor_pending_calls</td><td>reactor_pending_calls</td></tr>
+<tr><td>python_twisted_reactor_tick_time</td><td>reactor_tick_time</td></tr>
+</tbody></table>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 id="understanding-synapse-through-grafana-graphs"><a class="header" href="#understanding-synapse-through-grafana-graphs">Understanding Synapse through Grafana graphs</a></h2>
+<p>It is possible to monitor much of the internal state of Synapse using <a href="https://prometheus.io">Prometheus</a> 
+metrics and <a href="https://grafana.com/">Grafana</a>. 
+A guide for configuring Synapse to provide metrics is available <a href="usage/administration/../../metrics-howto.html">here</a> 
+and information on setting up Grafana is <a href="https://github.com/matrix-org/synapse/tree/master/contrib/grafana">here</a>.
+In this setup, Prometheus will periodically scrape the information Synapse provides and
+store a record of it over time. Grafana is then used as an interface to query and
+present this information through a series of pretty graphs.</p>
+<p>Once you have grafana set up, and assuming you're using <a href="https://github.com/matrix-org/synapse/blob/master/contrib/grafana/synapse.json">our grafana dashboard template</a>, look for the following graphs when debugging a slow/overloaded Synapse:</p>
+<h2 id="message-event-send-time"><a class="header" href="#message-event-send-time">Message Event Send Time</a></h2>
+<p><img src="https://user-images.githubusercontent.com/1342360/82239409-a1c8e900-9930-11ea-8081-e4614e0c63f4.png" alt="image" /></p>
+<p>This, along with the CPU and Memory graphs, is a good way to check the general health of your Synapse instance. It represents how long it takes for a user on your homeserver to send a message.</p>
+<h2 id="transaction-count-and-transaction-duration"><a class="header" href="#transaction-count-and-transaction-duration">Transaction Count and Transaction Duration</a></h2>
+<p><img src="https://user-images.githubusercontent.com/1342360/82239985-8d392080-9931-11ea-80d0-843ab2f22e1e.png" alt="image" /></p>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240050-ab068580-9931-11ea-98f1-f94671cbac9a.png" alt="image" /></p>
+<p>These graphs show the database transactions that are occurring the most frequently, as well as those are that are taking the most amount of time to execute.</p>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240192-e86b1300-9931-11ea-9aac-3e2c9bfa6fdc.png" alt="image" /></p>
+<p>In the first graph, we can see obvious spikes corresponding to lots of <code>get_user_by_id</code> transactions. This would be useful information to figure out which part of the Synapse codebase is potentially creating a heavy load on the system. However, be sure to cross-reference this with Transaction Duration, which states that <code>get_users_by_id</code> is actually a very quick database transaction and isn't causing as much load as others, like <code>persist_events</code>:</p>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240467-62030100-9932-11ea-8db9-917f2d977fe1.png" alt="image" /></p>
+<p>Still, it's probably worth investigating why we're getting users from the database that often, and whether it's possible to reduce the amount of queries we make by adjusting our cache factor(s).</p>
+<p>The <code>persist_events</code> transaction is responsible for saving new room events to the Synapse database, so can often show a high transaction duration.</p>
+<h2 id="federation"><a class="header" href="#federation">Federation</a></h2>
+<p>The charts in the &quot;Federation&quot; section show information about incoming and outgoing federation requests. Federation data can be divided into two basic types:</p>
+<ul>
+<li>PDU (Persistent Data Unit) - room events: messages, state events (join/leave), etc. These are permanently stored in the database.</li>
+<li>EDU (Ephemeral Data Unit) - other data, which need not be stored permanently, such as read receipts, typing notifications.</li>
+</ul>
+<p>The &quot;Outgoing EDUs by type&quot; chart shows the EDUs within outgoing federation requests by type: <code>m.device_list_update</code>, <code>m.direct_to_device</code>, <code>m.presence</code>, <code>m.receipt</code>, <code>m.typing</code>.</p>
+<p>If you see a large number of <code>m.presence</code> EDUs and are having trouble with too much CPU load, you can disable <code>presence</code> in the Synapse config. See also <a href="https://github.com/matrix-org/synapse/issues/3971">#3971</a>.</p>
+<h2 id="caches"><a class="header" href="#caches">Caches</a></h2>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240572-8b239180-9932-11ea-96ff-6b5f0e57ebe5.png" alt="image" /></p>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240666-b8703f80-9932-11ea-86af-9f663988d8da.png" alt="image" /></p>
+<p>This is quite a useful graph. It shows how many times Synapse attempts to retrieve a piece of data from a cache which the cache did not contain, thus resulting in a call to the database. We can see here that the <code>_get_joined_profile_from_event_id</code> cache is being requested a lot, and often the data we're after is not cached.</p>
+<p>Cross-referencing this with the Eviction Rate graph, which shows that entries are being evicted from <code>_get_joined_profile_from_event_id</code> quite often:</p>
+<p><img src="https://user-images.githubusercontent.com/1342360/82240766-de95df80-9932-11ea-8c15-5acfc57c48da.png" alt="image" /></p>
+<p>we should probably consider raising the size of that cache by raising its cache factor (a multiplier value for the size of an individual cache). Information on doing so is available <a href="https://github.com/matrix-org/synapse/blob/ee421e524478c1ad8d43741c27379499c2f6135c/docs/sample_config.yaml#L608-L642">here</a> (note that the configuration of individual cache factors through the configuration file is available in Synapse v1.14.0+, whereas doing so through environment variables has been supported for a very long time). Note that this will increase Synapse's overall memory usage.</p>
+<h2 id="forward-extremities"><a class="header" href="#forward-extremities">Forward Extremities</a></h2>
+<p><img src="https://user-images.githubusercontent.com/1342360/82241440-13566680-9934-11ea-8b88-ba468db937ed.png" alt="image" /></p>
+<p>Forward extremities are the leaf events at the end of a DAG in a room, aka events that have no children. The more that exist in a room, the more <a href="https://spec.matrix.org/v1.1/server-server-api/#room-state-resolution">state resolution</a> that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again.</p>
+<p>If a room has &gt;10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in <a href="https://github.com/matrix-org/synapse/issues/1760">#1760</a>.</p>
+<h2 id="garbage-collection"><a class="header" href="#garbage-collection">Garbage Collection</a></h2>
+<p><img src="https://user-images.githubusercontent.com/1342360/82241911-da6ac180-9934-11ea-9a0d-a311fe22acd0.png" alt="image" /></p>
+<p>Large spikes in garbage collection times (bigger than shown here, I'm talking in the 
+multiple seconds range), can cause lots of problems in Synapse performance. It's more an 
+indicator of problems, and a symptom of other problems though, so check other graphs for what might be causing it.</p>
+<h2 id="final-thoughts"><a class="header" href="#final-thoughts">Final Thoughts</a></h2>
+<p>If you're still having performance problems with your Synapse instance and you've 
+tried everything you can, it may just be a lack of system resources. Consider adding
+more CPU and RAM, and make use of <a href="usage/administration/../../workers.html">worker mode</a> 
+to make use of multiple CPU cores / multiple machines for your homeserver.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 id="some-useful-sql-queries-for-synapse-admins"><a class="header" href="#some-useful-sql-queries-for-synapse-admins">Some useful SQL queries for Synapse Admins</a></h2>
+<h2 id="size-of-full-matrix-db"><a class="header" href="#size-of-full-matrix-db">Size of full matrix db</a></h2>
+<p><code>SELECT pg_size_pretty( pg_database_size( 'matrix' ) );</code></p>
+<h3 id="result-example"><a class="header" href="#result-example">Result example:</a></h3>
+<pre><code>pg_size_pretty 
+----------------
+ 6420 MB
+(1 row)
+</code></pre>
+<h2 id="show-top-20-larger-rooms-by-state-events-count"><a class="header" href="#show-top-20-larger-rooms-by-state-events-count">Show top 20 larger rooms by state events count</a></h2>
+<pre><code class="language-sql">SELECT r.name, s.room_id, s.current_state_events
+  FROM room_stats_current s
+  LEFT JOIN room_stats_state r USING (room_id)
+  ORDER BY current_state_events DESC
+  LIMIT 20;
+</code></pre>
+<p>and by state_group_events count:</p>
+<pre><code class="language-sql">SELECT rss.name, s.room_id, count(s.room_id) FROM state_groups_state s
+LEFT JOIN room_stats_state rss USING (room_id)
+GROUP BY s.room_id, rss.name        
+ORDER BY count(s.room_id) DESC
+LIMIT 20;
+</code></pre>
+<p>plus same, but with join removed for performance reasons:</p>
+<pre><code class="language-sql">SELECT s.room_id, count(s.room_id) FROM state_groups_state s
+GROUP BY s.room_id        
+ORDER BY count(s.room_id) DESC
+LIMIT 20;
+</code></pre>
+<h2 id="show-top-20-larger-tables-by-row-count"><a class="header" href="#show-top-20-larger-tables-by-row-count">Show top 20 larger tables by row count</a></h2>
+<pre><code class="language-sql">SELECT relname, n_live_tup as rows
+  FROM pg_stat_user_tables 
+  ORDER BY n_live_tup DESC
+  LIMIT 20;
+</code></pre>
+<p>This query is quick, but may be very approximate, for exact number of rows use <code>SELECT COUNT(*) FROM &lt;table_name&gt;</code>.</p>
+<h3 id="result-example-1"><a class="header" href="#result-example-1">Result example:</a></h3>
+<pre><code>state_groups_state - 161687170
+event_auth - 8584785
+event_edges - 6995633
+event_json - 6585916
+event_reference_hashes - 6580990
+events - 6578879
+received_transactions - 5713989
+event_to_state_groups - 4873377
+stream_ordering_to_exterm - 4136285
+current_state_delta_stream - 3770972
+event_search - 3670521
+state_events - 2845082
+room_memberships - 2785854
+cache_invalidation_stream - 2448218
+state_groups - 1255467
+state_group_edges - 1229849
+current_state_events - 1222905
+users_in_public_rooms - 364059
+device_lists_stream - 326903
+user_directory_search - 316433
+</code></pre>
+<h2 id="show-top-20-rooms-by-new-events-count-in-last-1-day"><a class="header" href="#show-top-20-rooms-by-new-events-count-in-last-1-day">Show top 20 rooms by new events count in last 1 day:</a></h2>
+<pre><code class="language-sql">SELECT e.room_id, r.name, COUNT(e.event_id) cnt FROM events e
+LEFT JOIN room_stats_state r USING (room_id)
+WHERE e.origin_server_ts &gt;= DATE_PART('epoch', NOW() - INTERVAL '1 day') * 1000 GROUP BY e.room_id, r.name ORDER BY cnt DESC LIMIT 20;
+</code></pre>
+<h2 id="show-top-20-users-on-homeserver-by-sent-events-messages-at-last-month"><a class="header" href="#show-top-20-users-on-homeserver-by-sent-events-messages-at-last-month">Show top 20 users on homeserver by sent events (messages) at last month:</a></h2>
+<pre><code class="language-sql">SELECT user_id, SUM(total_events) 
+   FROM user_stats_historical
+   WHERE TO_TIMESTAMP(end_ts/1000) AT TIME ZONE 'UTC' &gt; date_trunc('day', now() - interval '1 month')
+   GROUP BY user_id
+   ORDER BY SUM(total_events) DESC 
+   LIMIT 20;
+</code></pre>
+<h2 id="show-last-100-messages-from-needed-user-with-room-names"><a class="header" href="#show-last-100-messages-from-needed-user-with-room-names">Show last 100 messages from needed user, with room names:</a></h2>
+<pre><code class="language-sql">SELECT e.room_id, r.name, e.event_id, e.type, e.content, j.json FROM events e
+  LEFT JOIN event_json j USING (room_id)
+  LEFT JOIN room_stats_state r USING (room_id)
+  WHERE sender = '@LOGIN:example.com'
+  AND e.type = 'm.room.message'
+  ORDER BY stream_ordering DESC
+  LIMIT 100;
+</code></pre>
+<h2 id="show-top-20-larger-tables-by-storage-size"><a class="header" href="#show-top-20-larger-tables-by-storage-size">Show top 20 larger tables by storage size</a></h2>
+<pre><code class="language-sql">SELECT nspname || '.' || relname AS &quot;relation&quot;,
+    pg_size_pretty(pg_total_relation_size(C.oid)) AS &quot;total_size&quot;
+  FROM pg_class C
+  LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+  WHERE nspname NOT IN ('pg_catalog', 'information_schema')
+    AND C.relkind &lt;&gt; 'i'
+    AND nspname !~ '^pg_toast'
+  ORDER BY pg_total_relation_size(C.oid) DESC
+  LIMIT 20;
+</code></pre>
+<h3 id="result-example-2"><a class="header" href="#result-example-2">Result example:</a></h3>
+<pre><code>public.state_groups_state - 27 GB
+public.event_json - 9855 MB
+public.events - 3675 MB
+public.event_edges - 3404 MB
+public.received_transactions - 2745 MB
+public.event_reference_hashes - 1864 MB
+public.event_auth - 1775 MB
+public.stream_ordering_to_exterm - 1663 MB
+public.event_search - 1370 MB
+public.room_memberships - 1050 MB
+public.event_to_state_groups - 948 MB
+public.current_state_delta_stream - 711 MB
+public.state_events - 611 MB
+public.presence_stream - 530 MB
+public.current_state_events - 525 MB
+public.cache_invalidation_stream - 466 MB
+public.receipts_linearized - 279 MB
+public.state_groups - 160 MB
+public.device_lists_remote_cache - 124 MB
+public.state_group_edges - 122 MB
+</code></pre>
+<h2 id="show-rooms-with-names-sorted-by-events-in-this-rooms"><a class="header" href="#show-rooms-with-names-sorted-by-events-in-this-rooms">Show rooms with names, sorted by events in this rooms</a></h2>
+<p><code>echo &quot;select event_json.room_id,room_stats_state.name from event_json,room_stats_state where room_stats_state.room_id=event_json.room_id&quot; | psql synapse | sort | uniq -c | sort -n</code></p>
+<h3 id="result-example-3"><a class="header" href="#result-example-3">Result example:</a></h3>
+<pre><code>   9459  !FPUfgzXYWTKgIrwKxW:matrix.org              | This Week in Matrix
+   9459  !FPUfgzXYWTKgIrwKxW:matrix.org              | This Week in Matrix (TWIM)
+  17799  !iDIOImbmXxwNngznsa:matrix.org              | Linux in Russian
+  18739  !GnEEPYXUhoaHbkFBNX:matrix.org              | Riot Android
+  23373  !QtykxKocfZaZOUrTwp:matrix.org              | Matrix HQ
+  39504  !gTQfWzbYncrtNrvEkB:matrix.org              | ru.[matrix]
+  43601  !iNmaIQExDMeqdITdHH:matrix.org              | Riot
+  43601  !iNmaIQExDMeqdITdHH:matrix.org              | Riot Web/Desktop
+</code></pre>
+<h2 id="lookup-room-state-info-by-list-of-room_id"><a class="header" href="#lookup-room-state-info-by-list-of-room_id">Lookup room state info by list of room_id</a></h2>
+<pre><code class="language-sql">SELECT rss.room_id, rss.name, rss.canonical_alias, rss.topic, rss.encryption, rsc.joined_members, rsc.local_users_in_room, rss.join_rules
+FROM room_stats_state rss
+LEFT JOIN room_stats_current rsc USING (room_id)
+WHERE room_id IN (WHERE room_id IN (
+ '!OGEhHVWSdvArJzumhm:matrix.org',
+ '!YTvKGNlinIzlkMTVRl:matrix.org'
+)
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><p>This blog post by Victor Berger explains how to use many of the tools listed on this page: https://levans.fr/shrink-synapse-database.html</p>
+<h1 id="list-of-useful-tools-and-scripts-for-maintenance-synapse-database"><a class="header" href="#list-of-useful-tools-and-scripts-for-maintenance-synapse-database">List of useful tools and scripts for maintenance Synapse database:</a></h1>
+<h2 id="a-hrefusageadministrationadmin_apimedia_admin_apihtmlpurge-remote-media-apipurge-remote-media-apia"><a class="header" href="#a-hrefusageadministrationadmin_apimedia_admin_apihtmlpurge-remote-media-apipurge-remote-media-apia"><a href="usage/administration/../../admin_api/media_admin_api.html#purge-remote-media-api">Purge Remote Media API</a></a></h2>
+<p>The purge remote media API allows server admins to purge old cached remote media.</p>
+<h2 id="a-hrefusageadministrationadmin_apimedia_admin_apihtmldelete-local-mediapurge-local-media-apia"><a class="header" href="#a-hrefusageadministrationadmin_apimedia_admin_apihtmldelete-local-mediapurge-local-media-apia"><a href="usage/administration/../../admin_api/media_admin_api.html#delete-local-media">Purge Local Media API</a></a></h2>
+<p>This API deletes the <em>local</em> media from the disk of your own server.</p>
+<h2 id="a-hrefusageadministrationadmin_apipurge_history_apihtmlpurge-history-apia"><a class="header" href="#a-hrefusageadministrationadmin_apipurge_history_apihtmlpurge-history-apia"><a href="usage/administration/../../admin_api/purge_history_api.html">Purge History API</a></a></h2>
+<p>The purge history API allows server admins to purge historic events from their database, reclaiming disk space.</p>
+<h2 id="a-hrefhttpsgithubcommatrix-orgrust-synapse-compress-statesynapse-compress-statea"><a class="header" href="#a-hrefhttpsgithubcommatrix-orgrust-synapse-compress-statesynapse-compress-statea"><a href="https://github.com/matrix-org/rust-synapse-compress-state">synapse-compress-state</a></a></h2>
+<p>Tool for compressing (deduplicating) <code>state_groups_state</code> table.</p>
+<h2 id="a-hrefusageadministrationuseful_sql_for_adminshtmlsql-for-analyzing-synapse-postgresql-database-statsa"><a class="header" href="#a-hrefusageadministrationuseful_sql_for_adminshtmlsql-for-analyzing-synapse-postgresql-database-statsa"><a href="usage/administration/useful_sql_for_admins.html">SQL for analyzing Synapse PostgreSQL database stats</a></a></h2>
+<p>Some easy SQL that reports useful stats about your Synapse database.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="how-do-state-groups-work"><a class="header" href="#how-do-state-groups-work">How do State Groups work?</a></h1>
+<p>As a general rule, I encourage people who want to understand the deepest darkest secrets of the database schema to drop by #synapse-dev:matrix.org and ask questions.</p>
+<p>However, one question that comes up frequently is that of how &quot;state groups&quot; work, and why the <code>state_groups_state</code> table gets so big, so here's an attempt to answer that question.</p>
+<p>We need to be able to relatively quickly calculate the state of a room at any point in that room's history. In other words, we need to know the state of the room at each event in that room. This is done as follows:</p>
+<p>A sequence of events where the state is the same are grouped together into a <code>state_group</code>; the mapping is recorded in <code>event_to_state_groups</code>. (Technically speaking, since a state event usually changes the state in the room, we are recording the state of the room <em>after</em> the given event id: which is to say, to a handwavey simplification, the first event in a state group is normally a state event, and others in the same state group are normally non-state-events.)</p>
+<p><code>state_groups</code> records, for each state group, the id of the room that we're looking at, and also the id of the first event in that group. (I'm not sure if that event id is used much in practice.) </p>
+<p>Now, if we stored all the room state for each <code>state_group</code>, that would be a huge amount of data. Instead, for each state group, we normally store the difference between the state in that group and some other state group, and only occasionally (every 100 state changes or so) record the full state.</p>
+<p>So, most state groups have an entry in <code>state_group_edges</code> (don't ask me why it's not a column in <code>state_groups</code>) which records the previous state group in the room, and <code>state_groups_state</code> records the differences in state since that previous state group.</p>
+<p>A full state group just records the event id for each piece of state in the room at that point.</p>
+<h2 id="known-bugs-with-state-groups"><a class="header" href="#known-bugs-with-state-groups">Known bugs with state groups</a></h2>
+<p>There are various reasons that we can end up creating many more state groups than we need: see https://github.com/matrix-org/synapse/issues/3364 for more details.</p>
+<h2 id="compression-tool"><a class="header" href="#compression-tool">Compression tool</a></h2>
+<p>There is a tool at https://github.com/matrix-org/rust-synapse-compress-state which can compress the <code>state_groups_state</code> on a room by-room basis (essentially, it reduces the number of &quot;full&quot; state groups). This can result in dramatic reductions of the storage used.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="request-log-format"><a class="header" href="#request-log-format">Request log format</a></h1>
+<p>HTTP request logs are written by synapse (see <a href="usage/administration/../synapse/http/site.py"><code>site.py</code></a> for details).</p>
+<p>See the following for how to decode the dense data available from the default logging configuration.</p>
+<pre><code>2020-10-01 12:00:00,000 - synapse.access.http.8008 - 311 - INFO - PUT-1000- 192.168.0.1 - 8008 - {another-matrix-server.com} Processed request: 0.100sec/-0.000sec (0.000sec, 0.000sec) (0.001sec/0.090sec/3) 11B !200 &quot;PUT /_matrix/federation/v1/send/1600000000000 HTTP/1.1&quot; &quot;Synapse/1.20.1&quot; [0 dbevts]
+-AAAAAAAAAAAAAAAAAAAAA-   -BBBBBBBBBBBBBBBBBBBBBB-   -C-   -DD-   -EEEEEE-  -FFFFFFFFF-   -GG-    -HHHHHHHHHHHHHHHHHHHHHHH-                     -IIIIII- -JJJJJJJ-  -KKKKKK-, -LLLLLL-  -MMMMMMM- -NNNNNN- O  -P- -QQ-  -RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR-   -SSSSSSSSSSSS-   -TTTTTT-
+</code></pre>
+<table><thead><tr><th>Part</th><th>Explanation</th></tr></thead><tbody>
+<tr><td>AAAA</td><td>Timestamp request was logged (not recieved)</td></tr>
+<tr><td>BBBB</td><td>Logger name (<code>synapse.access.(http\|https).&lt;tag&gt;</code>, where 'tag' is defined in the <code>listeners</code> config section, normally the port)</td></tr>
+<tr><td>CCCC</td><td>Line number in code</td></tr>
+<tr><td>DDDD</td><td>Log Level</td></tr>
+<tr><td>EEEE</td><td>Request Identifier (This identifier is shared by related log lines)</td></tr>
+<tr><td>FFFF</td><td>Source IP (Or X-Forwarded-For if enabled)</td></tr>
+<tr><td>GGGG</td><td>Server Port</td></tr>
+<tr><td>HHHH</td><td>Federated Server or Local User making request (blank if unauthenticated or not supplied)</td></tr>
+<tr><td>IIII</td><td>Total Time to process the request</td></tr>
+<tr><td>JJJJ</td><td>Time to send response over network once generated (this may be negative if the socket is closed before the response is generated)</td></tr>
+<tr><td>KKKK</td><td>Userland CPU time</td></tr>
+<tr><td>LLLL</td><td>System CPU time</td></tr>
+<tr><td>MMMM</td><td>Total time waiting for a free DB connection from the pool across all parallel DB work from this request</td></tr>
+<tr><td>NNNN</td><td>Total time waiting for response to DB queries across all parallel DB work from this request</td></tr>
+<tr><td>OOOO</td><td>Count of DB transactions performed</td></tr>
+<tr><td>PPPP</td><td>Response body size</td></tr>
+<tr><td>QQQQ</td><td>Response status code (prefixed with ! if the socket was closed before the response was generated)</td></tr>
+<tr><td>RRRR</td><td>Request</td></tr>
+<tr><td>SSSS</td><td>User-agent</td></tr>
+<tr><td>TTTT</td><td>Events fetched from DB to service this request (note that this does not include events fetched from the cache)</td></tr>
+</tbody></table>
+<p>MMMM / NNNN can be greater than IIII if there are multiple slow database queries
+running in parallel.</p>
+<p>Some actions can result in multiple identical http requests, which will return
+the same data, but only the first request will report time/transactions in
+<code>KKKK</code>/<code>LLLL</code>/<code>MMMM</code>/<code>NNNN</code>/<code>OOOO</code> - the others will be awaiting the first query to return a
+response and will simultaneously return with the first request, but with very
+small processing times.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 id="admin-faq"><a class="header" href="#admin-faq">Admin FAQ</a></h2>
+<h2 id="how-do-i-become-a-server-admin"><a class="header" href="#how-do-i-become-a-server-admin">How do I become a server admin?</a></h2>
+<p>If your server already has an admin account you should use the user admin API to promote other accounts to become admins. See <a href="usage/administration/../../admin_api/user_admin_api.html#Change-whether-a-user-is-a-server-administrator-or-not">User Admin API</a></p>
+<p>If you don't have any admin accounts yet you won't be able to use the admin API so you'll have to edit the database manually. Manually editing the database is generally not recommended so once you have an admin account, use the admin APIs to make further changes.</p>
+<pre><code class="language-sql">UPDATE users SET admin = 1 WHERE name = '@foo:bar.com';
+</code></pre>
+<h2 id="what-servers-are-my-server-talking-to"><a class="header" href="#what-servers-are-my-server-talking-to">What servers are my server talking to?</a></h2>
+<p>Run this sql query on your db:</p>
+<pre><code class="language-sql">SELECT * FROM destinations;
+</code></pre>
+<h2 id="what-servers-are-currently-participating-in-this-room"><a class="header" href="#what-servers-are-currently-participating-in-this-room">What servers are currently participating in this room?</a></h2>
+<p>Run this sql query on your db:</p>
+<pre><code class="language-sql">SELECT DISTINCT split_part(state_key, ':', 2)
+    FROM current_state_events AS c
+    INNER JOIN room_memberships AS m USING (room_id, event_id)
+    WHERE room_id = '!cURbafjkfsMDVwdRDQ:matrix.org' AND membership = 'join';
+</code></pre>
+<h2 id="what-users-are-registered-on-my-server"><a class="header" href="#what-users-are-registered-on-my-server">What users are registered on my server?</a></h2>
+<pre><code class="language-sql">SELECT NAME from users;
+</code></pre>
+<h2 id="manually-resetting-passwords"><a class="header" href="#manually-resetting-passwords">Manually resetting passwords:</a></h2>
+<p>See https://github.com/matrix-org/synapse/blob/master/README.rst#password-reset</p>
+<h2 id="i-have-a-problem-with-my-server-can-i-just-delete-my-database-and-start-again"><a class="header" href="#i-have-a-problem-with-my-server-can-i-just-delete-my-database-and-start-again">I have a problem with my server. Can I just delete my database and start again?</a></h2>
+<p>Deleting your database is unlikely to make anything better. </p>
+<p>It's easy to make the mistake of thinking that you can start again from a clean slate by dropping your database, but things don't work like that in a federated network: lots of other servers have information about your server.</p>
+<p>For example: other servers might think that you are in a room, your server will think that you are not, and you'll probably be unable to interact with that room in a sensible way ever again.</p>
+<p>In general, there are better solutions to any problem than dropping the database. Come and seek help in https://matrix.to/#/#synapse:matrix.org.</p>
+<p>There are two exceptions when it might be sensible to delete your database and start again:</p>
+<ul>
+<li>You have <em>never</em> joined any rooms which are federated with other servers. For instance, a local deployment which the outside world can't talk to. </li>
+<li>You are changing the <code>server_name</code> in the homeserver configuration. In effect this makes your server a completely new one from the point of view of the network, so in this case it makes sense to start with a clean database.
+(In both cases you probably also want to clear out the media_store.)</li>
+</ul>
+<h2 id="ive-stuffed-up-access-to-my-room-how-can-i-delete-it-to-free-up-the-alias"><a class="header" href="#ive-stuffed-up-access-to-my-room-how-can-i-delete-it-to-free-up-the-alias">I've stuffed up access to my room, how can I delete it to free up the alias?</a></h2>
+<p>Using the following curl command:</p>
+<pre><code>curl -H 'Authorization: Bearer &lt;access-token&gt;' -X DELETE https://matrix.org/_matrix/client/r0/directory/room/&lt;room-alias&gt;
+</code></pre>
+<p><code>&lt;access-token&gt;</code> - can be obtained in riot by looking in the riot settings, down the bottom is:
+Access Token:&lt;click to reveal&gt; </p>
+<p><code>&lt;room-alias&gt;</code> - the room alias, eg. #my_room:matrix.org this possibly needs to be URL encoded also, for example  %23my_room%3Amatrix.org</p>
+<h2 id="how-can-i-find-the-lines-corresponding-to-a-given-http-request-in-my-homeserver-log"><a class="header" href="#how-can-i-find-the-lines-corresponding-to-a-given-http-request-in-my-homeserver-log">How can I find the lines corresponding to a given HTTP request in my homeserver log?</a></h2>
+<p>Synapse tags each log line according to the HTTP request it is processing. When it finishes processing each request, it logs a line containing the words <code>Processed request: </code>. For example:</p>
+<pre><code>2019-02-14 22:35:08,196 - synapse.access.http.8008 - 302 - INFO - GET-37 - ::1 - 8008 - {@richvdh:localhost} Processed request: 0.173sec/0.001sec (0.002sec, 0.000sec) (0.027sec/0.026sec/2) 687B 200 &quot;GET /_matrix/client/r0/sync HTTP/1.1&quot; &quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36&quot; [0 dbevts]&quot;
+</code></pre>
+<p>Here we can see that the request has been tagged with <code>GET-37</code>. (The tag depends on the method of the HTTP request, so might start with <code>GET-</code>, <code>PUT-</code>, <code>POST-</code>, <code>OPTIONS-</code> or <code>DELETE-</code>.) So to find all lines corresponding to this request, we can do:</p>
+<pre><code>grep 'GET-37' homeserver.log
+</code></pre>
+<p>If you want to paste that output into a github issue or matrix room, please remember to surround it with triple-backticks (```) to make it legible (see https://help.github.com/en/articles/basic-writing-and-formatting-syntax#quoting-code).</p>
+<h2 id="what-do-all-those-fields-in-the-processed-line-mean"><a class="header" href="#what-do-all-those-fields-in-the-processed-line-mean">What do all those fields in the 'Processed' line mean?</a></h2>
+<p>See <a href="usage/administration/request_log.html">Request log format</a>.</p>
+<h2 id="what-are-the-biggest-rooms-on-my-server"><a class="header" href="#what-are-the-biggest-rooms-on-my-server">What are the biggest rooms on my server?</a></h2>
+<pre><code class="language-sql">SELECT s.canonical_alias, g.room_id, count(*) AS num_rows 
+FROM 
+  state_groups_state AS g, 
+  room_stats_state AS s 
+WHERE g.room_id = s.room_id 
+GROUP BY s.canonical_alias, g.room_id
+ORDER BY num_rows desc 
+LIMIT 10;
+</code></pre>
+<p>You can also use the <a href="usage/administration/../../admin_api/rooms.html#list-room-api">List Room API</a>
+and <code>order_by</code> <code>state_events</code>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="contributing"><a class="header" href="#contributing">Contributing</a></h1>
+<p>This document aims to get you started with contributing to Synapse!</p>
+<h1 id="1-who-can-contribute-to-synapse"><a class="header" href="#1-who-can-contribute-to-synapse">1. Who can contribute to Synapse?</a></h1>
+<p>Everyone is welcome to contribute code to <a href="https://github.com/matrix-org">matrix.org
+projects</a>, provided that they are willing to
+license their contributions under the same license as the project itself. We
+follow a simple 'inbound=outbound' model for contributions: the act of
+submitting an 'inbound' contribution means that the contributor agrees to
+license the code under the same terms as the project's overall 'outbound'
+license - in our case, this is almost always Apache Software License v2 (see
+<a href="https://github.com/matrix-org/synapse/blob/develop/LICENSE">LICENSE</a>).</p>
+<h1 id="2-what-do-i-need"><a class="header" href="#2-what-do-i-need">2. What do I need?</a></h1>
+<p>If you are running Windows, the Windows Subsystem for Linux (WSL) is strongly
+recommended for development. More information about WSL can be found at
+<a href="https://docs.microsoft.com/en-us/windows/wsl/install">https://docs.microsoft.com/en-us/windows/wsl/install</a>. Running Synapse natively
+on Windows is not officially supported.</p>
+<p>The code of Synapse is written in Python 3. To do pretty much anything, you'll need <a href="https://www.python.org/downloads/">a recent version of Python 3</a>. Your Python also needs support for <a href="https://docs.python.org/3/library/venv.html">virtual environments</a>. This is usually built-in, but some Linux distributions like Debian and Ubuntu split it out into its own package. Running <code>sudo apt install python3-venv</code> should be enough.</p>
+<p>Synapse can connect to PostgreSQL via the <a href="https://pypi.org/project/psycopg2/">psycopg2</a> Python library. Building this library from source requires access to PostgreSQL's C header files. On Debian or Ubuntu Linux, these can be installed with <code>sudo apt install libpq-dev</code>.</p>
+<p>The source code of Synapse is hosted on GitHub. You will also need <a href="https://github.com/git-guides/install-git">a recent version of git</a>.</p>
+<p>For some tests, you will need <a href="https://docs.docker.com/get-docker/">a recent version of Docker</a>.</p>
+<h1 id="3-get-the-source"><a class="header" href="#3-get-the-source">3. Get the source.</a></h1>
+<p>The preferred and easiest way to contribute changes is to fork the relevant
+project on GitHub, and then <a href="https://help.github.com/articles/using-pull-requests/">create a pull request</a> to ask us to pull your
+changes into our repo.</p>
+<p>Please base your changes on the <code>develop</code> branch.</p>
+<pre><code class="language-sh">git clone git@github.com:YOUR_GITHUB_USER_NAME/synapse.git
+git checkout develop
+</code></pre>
+<p>If you need help getting started with git, this is beyond the scope of the document, but you
+can find many good git tutorials on the web.</p>
+<h1 id="4-install-the-dependencies"><a class="header" href="#4-install-the-dependencies">4. Install the dependencies</a></h1>
+<p>Once you have installed Python 3 and added the source, please open a terminal and
+setup a <em>virtualenv</em>, as follows:</p>
+<pre><code class="language-sh">cd path/where/you/have/cloned/the/repository
+python3 -m venv ./env
+source ./env/bin/activate
+pip install wheel
+pip install -e &quot;.[all,dev]&quot;
+pip install tox
+</code></pre>
+<p>This will install the developer dependencies for the project.</p>
+<h1 id="5-get-in-touch"><a class="header" href="#5-get-in-touch">5. Get in touch.</a></h1>
+<p>Join our developer community on Matrix: <a href="https://matrix.to/#/#synapse-dev:matrix.org">#synapse-dev:matrix.org</a>!</p>
+<h1 id="6-pick-an-issue"><a class="header" href="#6-pick-an-issue">6. Pick an issue.</a></h1>
+<p>Fix your favorite problem or perhaps find a <a href="https://github.com/matrix-org/synapse/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22">Good First Issue</a>
+to work on.</p>
+<h1 id="7-turn-coffee-into-code-and-documentation"><a class="header" href="#7-turn-coffee-into-code-and-documentation">7. Turn coffee into code and documentation!</a></h1>
+<p>There is a growing amount of documentation located in the
+<a href="https://github.com/matrix-org/synapse/tree/develop/docs"><code>docs</code></a>
+directory, with a rendered version <a href="https://matrix-org.github.io/synapse">available online</a>.
+This documentation is intended primarily for sysadmins running their
+own Synapse instance, as well as developers interacting externally with
+Synapse.
+<a href="https://github.com/matrix-org/synapse/tree/develop/docs/development"><code>docs/development</code></a>
+exists primarily to house documentation for
+Synapse developers.
+<a href="https://github.com/matrix-org/synapse/tree/develop/docs/admin_api"><code>docs/admin_api</code></a> houses documentation
+regarding Synapse's Admin API, which is used mostly by sysadmins and external
+service developers.</p>
+<p>Synapse's code style is documented <a href="development/../code_style.html">here</a>. Please follow
+it, including the conventions for the <a href="development/../code_style.html#configuration-file-format">sample configuration
+file</a>.</p>
+<p>We welcome improvements and additions to our documentation itself! When
+writing new pages, please
+<a href="https://github.com/matrix-org/synapse/tree/develop/docs#adding-to-the-documentation">build <code>docs</code> to a book</a>
+to check that your contributions render correctly. The docs are written in
+<a href="https://guides.github.com/features/mastering-markdown/">GitHub-Flavoured Markdown</a>.</p>
+<p>Some documentation also exists in <a href="https://github.com/matrix-org/synapse/wiki">Synapse's GitHub
+Wiki</a>, although this is primarily
+contributed to by community authors.</p>
+<h1 id="8-test-test-test"><a class="header" href="#8-test-test-test">8. Test, test, test!</a></h1>
+<p><a name="test-test-test"></a></p>
+<p>While you're developing and before submitting a patch, you'll
+want to test your code.</p>
+<h2 id="run-the-linters"><a class="header" href="#run-the-linters">Run the linters.</a></h2>
+<p>The linters look at your code and do two things:</p>
+<ul>
+<li>ensure that your code follows the coding style adopted by the project;</li>
+<li>catch a number of errors in your code.</li>
+</ul>
+<p>The linter takes no time at all to run as soon as you've <a href="development/contributing_guide.html#4-install-the-dependencies">downloaded the dependencies into your python virtual environment</a>.</p>
+<pre><code class="language-sh">source ./env/bin/activate
+./scripts-dev/lint.sh
+</code></pre>
+<p>Note that this script <em>will modify your files</em> to fix styling errors.
+Make sure that you have saved all your files.</p>
+<p>If you wish to restrict the linters to only the files changed since the last commit
+(much faster!), you can instead run:</p>
+<pre><code class="language-sh">source ./env/bin/activate
+./scripts-dev/lint.sh -d
+</code></pre>
+<p>Or if you know exactly which files you wish to lint, you can instead run:</p>
+<pre><code class="language-sh">source ./env/bin/activate
+./scripts-dev/lint.sh path/to/file1.py path/to/file2.py path/to/folder
+</code></pre>
+<h2 id="run-the-unit-tests-twisted-trial"><a class="header" href="#run-the-unit-tests-twisted-trial">Run the unit tests (Twisted trial).</a></h2>
+<p>The unit tests run parts of Synapse, including your changes, to see if anything
+was broken. They are slower than the linters but will typically catch more errors.</p>
+<pre><code class="language-sh">source ./env/bin/activate
+trial tests
+</code></pre>
+<p>If you wish to only run <em>some</em> unit tests, you may specify
+another module instead of <code>tests</code> - or a test class or a method:</p>
+<pre><code class="language-sh">source ./env/bin/activate
+trial tests.rest.admin.test_room tests.handlers.test_admin.ExfiltrateData.test_invite
+</code></pre>
+<p>If your tests fail, you may wish to look at the logs (the default log level is <code>ERROR</code>):</p>
+<pre><code class="language-sh">less _trial_temp/test.log
+</code></pre>
+<p>To increase the log level for the tests, set <code>SYNAPSE_TEST_LOG_LEVEL</code>:</p>
+<pre><code class="language-sh">SYNAPSE_TEST_LOG_LEVEL=DEBUG trial tests
+</code></pre>
+<p>By default, tests will use an in-memory SQLite database for test data. For additional
+help with debugging, one can use an on-disk SQLite database file instead, in order to
+review database state during and after running tests. This can be done by setting
+the <code>SYNAPSE_TEST_PERSIST_SQLITE_DB</code> environment variable. Doing so will cause the
+database state to be stored in a file named <code>test.db</code> under the trial process'
+working directory. Typically, this ends up being <code>_trial_temp/test.db</code>. For example:</p>
+<pre><code class="language-sh">SYNAPSE_TEST_PERSIST_SQLITE_DB=1 trial tests
+</code></pre>
+<p>The database file can then be inspected with:</p>
+<pre><code class="language-sh">sqlite3 _trial_temp/test.db
+</code></pre>
+<p>Note that the database file is cleared at the beginning of each test run. Thus it 
+will always only contain the data generated by the <em>last run test</em>. Though generally
+when debugging, one is only running a single test anyway.</p>
+<h3 id="running-tests-under-postgresql"><a class="header" href="#running-tests-under-postgresql">Running tests under PostgreSQL</a></h3>
+<p>Invoking <code>trial</code> as above will use an in-memory SQLite database. This is great for
+quick development and testing. However, we recommend using a PostgreSQL database
+in production (and indeed, we have some code paths specific to each database).
+This means that we need to run our unit tests against PostgreSQL too. Our CI does
+this automatically for pull requests and release candidates, but it's sometimes
+useful to reproduce this locally.</p>
+<p>To do so, <a href="development/../postgres.html">configure Postgres</a> and run <code>trial</code> with the
+following environment variables matching your configuration:</p>
+<ul>
+<li><code>SYNAPSE_POSTGRES</code> to anything nonempty</li>
+<li><code>SYNAPSE_POSTGRES_HOST</code></li>
+<li><code>SYNAPSE_POSTGRES_USER</code></li>
+<li><code>SYNAPSE_POSTGRES_PASSWORD</code></li>
+</ul>
+<p>For example:</p>
+<pre><code class="language-shell">export SYNAPSE_POSTGRES=1
+export SYNAPSE_POSTGRES_HOST=localhost
+export SYNAPSE_POSTGRES_USER=postgres
+export SYNAPSE_POSTGRES_PASSWORD=mydevenvpassword
+trial
+</code></pre>
+<h4 id="prebuilt-container"><a class="header" href="#prebuilt-container">Prebuilt container</a></h4>
+<p>Since configuring PostgreSQL can be fiddly, we can make use of a pre-made
+Docker container to set up PostgreSQL and run our tests for us. To do so, run</p>
+<pre><code class="language-shell">scripts-dev/test_postgresql.sh
+</code></pre>
+<p>Any extra arguments to the script will be passed to <code>tox</code> and then to <code>trial</code>,
+so we can run a specific test in this container with e.g.</p>
+<pre><code class="language-shell">scripts-dev/test_postgresql.sh tests.replication.test_sharded_event_persister.EventPersisterShardTestCase
+</code></pre>
+<p>The container creates a folder in your Synapse checkout called
+<code>.tox-pg-container</code> and uses this as a tox environment. The output of any
+<code>trial</code> runs goes into <code>_trial_temp</code> in your synapse source directory — the same
+as running <code>trial</code> directly on your host machine.</p>
+<h2 id="run-the-integration-tests-a-hrefhttpsgithubcommatrix-orgsytestsytesta"><a class="header" href="#run-the-integration-tests-a-hrefhttpsgithubcommatrix-orgsytestsytesta">Run the integration tests (<a href="https://github.com/matrix-org/sytest">Sytest</a>).</a></h2>
+<p>The integration tests are a more comprehensive suite of tests. They
+run a full version of Synapse, including your changes, to check if
+anything was broken. They are slower than the unit tests but will
+typically catch more errors.</p>
+<p>The following command will let you run the integration test with the most common
+configuration:</p>
+<pre><code class="language-sh">$ docker run --rm -it -v /path/where/you/have/cloned/the/repository\:/src:ro -v /path/to/where/you/want/logs\:/logs matrixdotorg/sytest-synapse:buster
+</code></pre>
+<p>This configuration should generally cover  your needs. For more details about other configurations, see <a href="https://github.com/matrix-org/sytest/blob/develop/docker/README.md">documentation in the SyTest repo</a>.</p>
+<h2 id="run-the-integration-tests-a-hrefhttpsgithubcommatrix-orgcomplementcomplementa"><a class="header" href="#run-the-integration-tests-a-hrefhttpsgithubcommatrix-orgcomplementcomplementa">Run the integration tests (<a href="https://github.com/matrix-org/complement">Complement</a>).</a></h2>
+<p><a href="https://github.com/matrix-org/complement">Complement</a> is a suite of black box tests that can be run on any homeserver implementation. It can also be thought of as end-to-end (e2e) tests.</p>
+<p>It's often nice to develop on Synapse and write Complement tests at the same time.
+Here is how to run your local Synapse checkout against your local Complement checkout.</p>
+<p>(checkout <a href="https://github.com/matrix-org/complement"><code>complement</code></a> alongside your <code>synapse</code> checkout)</p>
+<pre><code class="language-sh">COMPLEMENT_DIR=../complement ./scripts-dev/complement.sh
+</code></pre>
+<p>To run a specific test file, you can pass the test name at the end of the command. The name passed comes from the naming structure in your Complement tests. If you're unsure of the name, you can do a full run and copy it from the test output:</p>
+<pre><code class="language-sh">COMPLEMENT_DIR=../complement ./scripts-dev/complement.sh TestBackfillingHistory
+</code></pre>
+<p>To run a specific test, you can specify the whole name structure:</p>
+<pre><code class="language-sh">COMPLEMENT_DIR=../complement ./scripts-dev/complement.sh TestBackfillingHistory/parallel/Backfilled_historical_events_resolve_with_proper_state_in_correct_order
+</code></pre>
+<h3 id="access-database-for-homeserver-after-complement-test-runs"><a class="header" href="#access-database-for-homeserver-after-complement-test-runs">Access database for homeserver after Complement test runs.</a></h3>
+<p>If you're curious what the database looks like after you run some tests, here are some steps to get you going in Synapse:</p>
+<ol>
+<li>In your Complement test comment out <code>defer deployment.Destroy(t)</code> and replace with <code>defer time.Sleep(2 * time.Hour)</code> to keep the homeserver running after the tests complete</li>
+<li>Start the Complement tests</li>
+<li>Find the name of the container, <code>docker ps -f name=complement_</code> (this will filter for just the Compelement related Docker containers)</li>
+<li>Access the container replacing the name with what you found in the previous step: <code>docker exec -it complement_1_hs_with_application_service.hs1_2 /bin/bash</code></li>
+<li>Install sqlite (database driver), <code>apt-get update &amp;&amp; apt-get install -y sqlite3</code></li>
+<li>Then run <code>sqlite3</code> and open the database <code>.open /conf/homeserver.db</code> (this db path comes from the Synapse homeserver.yaml)</li>
+</ol>
+<h1 id="9-submit-your-patch"><a class="header" href="#9-submit-your-patch">9. Submit your patch.</a></h1>
+<p>Once you're happy with your patch, it's time to prepare a Pull Request.</p>
+<p>To prepare a Pull Request, please:</p>
+<ol>
+<li>verify that <a href="development/contributing_guide.html#test-test-test">all the tests pass</a>, including the coding style;</li>
+<li><a href="development/contributing_guide.html#sign-off">sign off</a> your contribution;</li>
+<li><code>git push</code> your commit to your fork of Synapse;</li>
+<li>on GitHub, <a href="https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request">create the Pull Request</a>;</li>
+<li>add a <a href="development/contributing_guide.html#changelog">changelog entry</a> and push it to your Pull Request;</li>
+<li>for most contributors, that's all - however, if you are a member of the organization <code>matrix-org</code>, on GitHub, please request a review from <code>matrix.org / Synapse Core</code>.</li>
+<li>if you need to update your PR, please avoid rebasing and just add new commits to your branch.</li>
+</ol>
+<h2 id="changelog"><a class="header" href="#changelog">Changelog</a></h2>
+<p>All changes, even minor ones, need a corresponding changelog / newsfragment
+entry. These are managed by <a href="https://github.com/hawkowl/towncrier">Towncrier</a>.</p>
+<p>To create a changelog entry, make a new file in the <code>changelog.d</code> directory named
+in the format of <code>PRnumber.type</code>. The type can be one of the following:</p>
+<ul>
+<li><code>feature</code></li>
+<li><code>bugfix</code></li>
+<li><code>docker</code> (for updates to the Docker image)</li>
+<li><code>doc</code> (for updates to the documentation)</li>
+<li><code>removal</code> (also used for deprecations)</li>
+<li><code>misc</code> (for internal-only changes)</li>
+</ul>
+<p>This file will become part of our <a href="https://github.com/matrix-org/synapse/blob/master/CHANGES.md">changelog</a> at the next
+release, so the content of the file should be a short description of your
+change in the same style as the rest of the changelog. The file can contain Markdown
+formatting, and should end with a full stop (.) or an exclamation mark (!) for
+consistency.</p>
+<p>Adding credits to the changelog is encouraged, we value your
+contributions and would like to have you shouted out in the release notes!</p>
+<p>For example, a fix in PR #1234 would have its changelog entry in
+<code>changelog.d/1234.bugfix</code>, and contain content like:</p>
+<blockquote>
+<p>The security levels of Florbs are now validated when received
+via the <code>/federation/florb</code> endpoint. Contributed by Jane Matrix.</p>
+</blockquote>
+<p>If there are multiple pull requests involved in a single bugfix/feature/etc,
+then the content for each <code>changelog.d</code> file should be the same. Towncrier will
+merge the matching files together into a single changelog entry when we come to
+release.</p>
+<h3 id="how-do-i-know-what-to-call-the-changelog-file-before-i-create-the-pr"><a class="header" href="#how-do-i-know-what-to-call-the-changelog-file-before-i-create-the-pr">How do I know what to call the changelog file before I create the PR?</a></h3>
+<p>Obviously, you don't know if you should call your newsfile
+<code>1234.bugfix</code> or <code>5678.bugfix</code> until you create the PR, which leads to a
+chicken-and-egg problem.</p>
+<p>There are two options for solving this:</p>
+<ol>
+<li>
+<p>Open the PR without a changelog file, see what number you got, and <em>then</em>
+add the changelog file to your branch (see <a href="development/contributing_guide.html#updating-your-pull-request">Updating your pull
+request</a>), or:</p>
+</li>
+<li>
+<p>Look at the <a href="https://github.com/matrix-org/synapse/issues?q=">list of all
+issues/PRs</a>, add one to the
+highest number you see, and quickly open the PR before somebody else claims
+your number.</p>
+<p><a href="https://github.com/richvdh/scripts/blob/master/next_github_number.sh">This
+script</a>
+might be helpful if you find yourself doing this a lot.</p>
+</li>
+</ol>
+<p>Sorry, we know it's a bit fiddly, but it's <em>really</em> helpful for us when we come
+to put together a release!</p>
+<h3 id="debian-changelog"><a class="header" href="#debian-changelog">Debian changelog</a></h3>
+<p>Changes which affect the debian packaging files (in <code>debian</code>) are an
+exception to the rule that all changes require a <code>changelog.d</code> file.</p>
+<p>In this case, you will need to add an entry to the debian changelog for the
+next release. For this, run the following command:</p>
+<pre><code>dch
+</code></pre>
+<p>This will make up a new version number (if there isn't already an unreleased
+version in flight), and open an editor where you can add a new changelog entry.
+(Our release process will ensure that the version number and maintainer name is
+corrected for the release.)</p>
+<p>If your change affects both the debian packaging <em>and</em> files outside the debian
+directory, you will need both a regular newsfragment <em>and</em> an entry in the
+debian changelog. (Though typically such changes should be submitted as two
+separate pull requests.)</p>
+<h2 id="sign-off"><a class="header" href="#sign-off">Sign off</a></h2>
+<p>In order to have a concrete record that your contribution is intentional
+and you agree to license it under the same terms as the project's license, we've adopted the
+same lightweight approach that the Linux Kernel
+<a href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin%3E">submitting patches process</a>,
+<a href="https://github.com/docker/docker/blob/master/CONTRIBUTING.md">Docker</a>, and many other
+projects use: the DCO (Developer Certificate of Origin:
+http://developercertificate.org/). This is a simple declaration that you wrote
+the contribution or otherwise have the right to contribute it to Matrix:</p>
+<pre><code>Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+660 York Street, Suite 102,
+San Francisco, CA 94110 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+</code></pre>
+<p>If you agree to this for your contribution, then all that's needed is to
+include the line in your commit or pull request comment:</p>
+<pre><code>Signed-off-by: Your Name &lt;your@email.example.org&gt;
+</code></pre>
+<p>We accept contributions under a legally identifiable name, such as
+your name on government documentation or common-law names (names
+claimed by legitimate usage or repute). Unfortunately, we cannot
+accept anonymous contributions at this time.</p>
+<p>Git allows you to add this signoff automatically when using the <code>-s</code>
+flag to <code>git commit</code>, which uses the name and email set in your
+<code>user.name</code> and <code>user.email</code> git configs.</p>
+<h3 id="private-sign-off"><a class="header" href="#private-sign-off">Private Sign off</a></h3>
+<p>If you would like to provide your legal name privately to the Matrix.org
+Foundation (instead of in a public commit or comment), you can do so
+by emailing your legal name and a link to the pull request to
+<a href="mailto:dco@matrix.org?subject=Private%20sign%20off">dco@matrix.org</a>.
+It helps to include &quot;sign off&quot; or similar in the subject line. You will then
+be instructed further.</p>
+<p>Once private sign off is complete, doing so for future contributions will not
+be required.</p>
+<h1 id="10-turn-feedback-into-better-code"><a class="header" href="#10-turn-feedback-into-better-code">10. Turn feedback into better code.</a></h1>
+<p>Once the Pull Request is opened, you will see a few things:</p>
+<ol>
+<li>our automated CI (Continuous Integration) pipeline will run (again) the linters, the unit tests, the integration tests and more;</li>
+<li>one or more of the developers will take a look at your Pull Request and offer feedback.</li>
+</ol>
+<p>From this point, you should:</p>
+<ol>
+<li>Look at the results of the CI pipeline.
+<ul>
+<li>If there is any error, fix the error.</li>
+</ul>
+</li>
+<li>If a developer has requested changes, make these changes and let us know if it is ready for a developer to review again.</li>
+<li>Create a new commit with the changes.
+<ul>
+<li>Please do NOT overwrite the history. New commits make the reviewer's life easier.</li>
+<li>Push this commits to your Pull Request.</li>
+</ul>
+</li>
+<li>Back to 1.</li>
+</ol>
+<p>Once both the CI and the developers are happy, the patch will be merged into Synapse and released shortly!</p>
+<h1 id="11-find-a-new-issue"><a class="header" href="#11-find-a-new-issue">11. Find a new issue.</a></h1>
+<p>By now, you know the drill!</p>
+<h1 id="notes-for-maintainers-on-merging-prs-etc"><a class="header" href="#notes-for-maintainers-on-merging-prs-etc">Notes for maintainers on merging PRs etc</a></h1>
+<p>There are some notes for those with commit access to the project on how we
+manage git <a href="development/git.html">here</a>.</p>
+<h1 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h1>
+<p>That's it! Matrix is a very open and collaborative project as you might expect
+given our obsession with open communication. If we're going to successfully
+matrix together all the fragmented communication technologies out there we are
+reliant on contributions and collaboration from the community to do so. So
+please get involved - and we hope you have as much fun hacking on Matrix as we
+do!</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="code-style"><a class="header" href="#code-style">Code Style</a></h1>
+<h2 id="formatting-tools"><a class="header" href="#formatting-tools">Formatting tools</a></h2>
+<p>The Synapse codebase uses a number of code formatting tools in order to
+quickly and automatically check for formatting (and sometimes logical)
+errors in code.</p>
+<p>The necessary tools are detailed below.</p>
+<p>First install them with:</p>
+<pre><code class="language-sh">pip install -e &quot;.[lint,mypy]&quot;
+</code></pre>
+<ul>
+<li>
+<p><strong>black</strong></p>
+<p>The Synapse codebase uses <a href="https://pypi.org/project/black/">black</a>
+as an opinionated code formatter, ensuring all comitted code is
+properly formatted.</p>
+<p>Have <code>black</code> auto-format your code (it shouldn't change any
+functionality) with:</p>
+<pre><code class="language-sh">black . --exclude=&quot;\.tox|build|env&quot;
+</code></pre>
+</li>
+<li>
+<p><strong>flake8</strong></p>
+<p><code>flake8</code> is a code checking tool. We require code to pass <code>flake8</code>
+before being merged into the codebase.</p>
+<p>Check all application and test code with:</p>
+<pre><code class="language-sh">flake8 synapse tests
+</code></pre>
+</li>
+<li>
+<p><strong>isort</strong></p>
+<p><code>isort</code> ensures imports are nicely formatted, and can suggest and
+auto-fix issues such as double-importing.</p>
+<p>Auto-fix imports with:</p>
+<pre><code class="language-sh">isort -rc synapse tests
+</code></pre>
+<p><code>-rc</code> means to recursively search the given directories.</p>
+</li>
+</ul>
+<p>It's worth noting that modern IDEs and text editors can run these tools
+automatically on save. It may be worth looking into whether this
+functionality is supported in your editor for a more convenient
+development workflow. It is not, however, recommended to run <code>flake8</code> on
+save as it takes a while and is very resource intensive.</p>
+<h2 id="general-rules"><a class="header" href="#general-rules">General rules</a></h2>
+<ul>
+<li><strong>Naming</strong>:
+<ul>
+<li>Use camel case for class and type names</li>
+<li>Use underscores for functions and variables.</li>
+</ul>
+</li>
+<li><strong>Docstrings</strong>: should follow the <a href="https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings">google code
+style</a>.
+See the
+<a href="http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html">examples</a>
+in the sphinx documentation.</li>
+<li><strong>Imports</strong>:
+<ul>
+<li>
+<p>Imports should be sorted by <code>isort</code> as described above.</p>
+</li>
+<li>
+<p>Prefer to import classes and functions rather than packages or
+modules.</p>
+<p>Example:</p>
+<pre><code class="language-python">from synapse.types import UserID
+...
+user_id = UserID(local, server)
+</code></pre>
+<p>is preferred over:</p>
+<pre><code class="language-python">from synapse import types
+...
+user_id = types.UserID(local, server)
+</code></pre>
+<p>(or any other variant).</p>
+<p>This goes against the advice in the Google style guide, but it
+means that errors in the name are caught early (at import time).</p>
+</li>
+<li>
+<p>Avoid wildcard imports (<code>from synapse.types import *</code>) and
+relative imports (<code>from .types import UserID</code>).</p>
+</li>
+</ul>
+</li>
+</ul>
+<h2 id="configuration-file-format"><a class="header" href="#configuration-file-format">Configuration file format</a></h2>
+<p>The <a href="./sample_config.yaml">sample configuration file</a> acts as a
+reference to Synapse's configuration options for server administrators.
+Remember that many readers will be unfamiliar with YAML and server
+administration in general, so that it is important that the file be as
+easy to understand as possible, which includes following a consistent
+format.</p>
+<p>Some guidelines follow:</p>
+<ul>
+<li>
+<p>Sections should be separated with a heading consisting of a single
+line prefixed and suffixed with <code>##</code>. There should be <strong>two</strong> blank
+lines before the section header, and <strong>one</strong> after.</p>
+</li>
+<li>
+<p>Each option should be listed in the file with the following format:</p>
+<ul>
+<li>
+<p>A comment describing the setting. Each line of this comment
+should be prefixed with a hash (<code>#</code>) and a space.</p>
+<p>The comment should describe the default behaviour (ie, what
+happens if the setting is omitted), as well as what the effect
+will be if the setting is changed.</p>
+<p>Often, the comment end with something like &quot;uncomment the
+following to <do action>&quot;.</p>
+</li>
+<li>
+<p>A line consisting of only <code>#</code>.</p>
+</li>
+<li>
+<p>A commented-out example setting, prefixed with only <code>#</code>.</p>
+<p>For boolean (on/off) options, convention is that this example
+should be the <em>opposite</em> to the default (so the comment will end
+with &quot;Uncomment the following to enable [or disable]
+<feature>.&quot; For other options, the example should give some
+non-default value which is likely to be useful to the reader.</p>
+</li>
+</ul>
+</li>
+<li>
+<p>There should be a blank line between each option.</p>
+</li>
+<li>
+<p>Where several settings are grouped into a single dict, <em>avoid</em> the
+convention where the whole block is commented out, resulting in
+comment lines starting <code># #</code>, as this is hard to read and confusing
+to edit. Instead, leave the top-level config option uncommented, and
+follow the conventions above for sub-options. Ensure that your code
+correctly handles the top-level option being set to <code>None</code> (as it
+will be if no sub-options are enabled).</p>
+</li>
+<li>
+<p>Lines should be wrapped at 80 characters.</p>
+</li>
+<li>
+<p>Use two-space indents.</p>
+</li>
+<li>
+<p><code>true</code> and <code>false</code> are spelt thus (as opposed to <code>True</code>, etc.)</p>
+</li>
+<li>
+<p>Use single quotes (<code>'</code>) rather than double-quotes (<code>&quot;</code>) or backticks
+(<code>`</code>) to refer to configuration options.</p>
+</li>
+</ul>
+<p>Example:</p>
+<pre><code class="language-yaml">## Frobnication ##
+
+# The frobnicator will ensure that all requests are fully frobnicated.
+# To enable it, uncomment the following.
+#
+#frobnicator_enabled: true
+
+# By default, the frobnicator will frobnicate with the default frobber.
+# The following will make it use an alternative frobber.
+#
+#frobincator_frobber: special_frobber
+
+# Settings for the frobber
+#
+frobber:
+  # frobbing speed. Defaults to 1.
+  #
+  #speed: 10
+
+  # frobbing distance. Defaults to 1000.
+  #
+  #distance: 100
+</code></pre>
+<p>Note that the sample configuration is generated from the synapse code
+and is maintained by a script, <code>scripts-dev/generate_sample_config.sh</code>.
+Making sure that the output from this script matches the desired format
+is left as an exercise for the reader!</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="synapse-release-cycle"><a class="header" href="#synapse-release-cycle">Synapse Release Cycle</a></h1>
+<p>Releases of Synapse follow a two week release cycle with new releases usually
+occurring on Tuesdays:</p>
+<ul>
+<li>Day 0: Synapse <code>N - 1</code> is released.</li>
+<li>Day 7: Synapse <code>N</code> release candidate 1 is released.</li>
+<li>Days 7 - 13: Synapse <code>N</code> release candidates 2+ are released, if bugs are found.</li>
+<li>Day 14: Synapse <code>N</code> is released.</li>
+</ul>
+<p>Note that this schedule might be modified depending on the availability of the
+Synapse team, e.g. releases may be skipped to avoid holidays.</p>
+<p>Release announcements can be found in the
+<a href="https://matrix.org/blog/category/releases">release category of the Matrix blog</a>.</p>
+<h2 id="bugfix-releases"><a class="header" href="#bugfix-releases">Bugfix releases</a></h2>
+<p>If a bug is found after release that is deemed severe enough (by a combination
+of the impacted users and the impact on those users) then a bugfix release may
+be issued. This may be at any point in the release cycle.</p>
+<h2 id="security-releases"><a class="header" href="#security-releases">Security releases</a></h2>
+<p>Security will sometimes be backported to the previous version and released
+immediately before the next release candidate. An example of this might be:</p>
+<ul>
+<li>Day 0: Synapse N - 1 is released.</li>
+<li>Day 7: Synapse (N - 1).1 is released as Synapse N - 1 + the security fix.</li>
+<li>Day 7: Synapse N release candidate 1 is released (including the security fix).</li>
+</ul>
+<p>Depending on the impact and complexity of security fixes, multiple fixes might
+be held to be released together.</p>
+<p>In some cases, a pre-disclosure of a security release will be issued as a notice
+to Synapse operators that there is an upcoming security release. These can be
+found in the <a href="https://matrix.org/blog/category/security">security category of the Matrix blog</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="some-notes-on-how-we-use-git"><a class="header" href="#some-notes-on-how-we-use-git">Some notes on how we use git</a></h1>
+<h2 id="on-keeping-the-commit-history-clean"><a class="header" href="#on-keeping-the-commit-history-clean">On keeping the commit history clean</a></h2>
+<p>In an ideal world, our git commit history would be a linear progression of
+commits each of which contains a single change building on what came
+before. Here, by way of an arbitrary example, is the top of <code>git log --graph b2dba0607</code>:</p>
+<img src="development/img/git/clean.png" alt="clean git graph" width="500px">
+<p>Note how the commit comment explains clearly what is changing and why. Also
+note the <em>absence</em> of merge commits, as well as the absence of commits called
+things like (to pick a few culprits):
+<a href="https://github.com/matrix-org/synapse/commit/84691da6c">“pep8”</a>, <a href="https://github.com/matrix-org/synapse/commit/474810d9d">“fix broken
+test”</a>,
+<a href="https://github.com/matrix-org/synapse/commit/c9d72e457">“oops”</a>,
+<a href="https://github.com/matrix-org/synapse/commit/836358823">“typo”</a>, or <a href="https://github.com/matrix-org/synapse/commit/707374d5d">“Who's
+the president?”</a>.</p>
+<p>There are a number of reasons why keeping a clean commit history is a good
+thing:</p>
+<ul>
+<li>
+<p>From time to time, after a change lands, it turns out to be necessary to
+revert it, or to backport it to a release branch. Those operations are
+<em>much</em> easier when the change is contained in a single commit.</p>
+</li>
+<li>
+<p>Similarly, it's much easier to answer questions like “is the fix for
+<code>/publicRooms</code> on the release branch?” if that change consists of a single
+commit.</p>
+</li>
+<li>
+<p>Likewise: “what has changed on this branch in the last week?” is much
+clearer without merges and “pep8” commits everywhere.</p>
+</li>
+<li>
+<p>Sometimes we need to figure out where a bug got introduced, or some
+behaviour changed. One way of doing that is with <code>git bisect</code>: pick an
+arbitrary commit between the known good point and the known bad point, and
+see how the code behaves. However, that strategy fails if the commit you
+chose is the middle of someone's epic branch in which they broke the world
+before putting it back together again.</p>
+</li>
+</ul>
+<p>One counterargument is that it is sometimes useful to see how a PR evolved as
+it went through review cycles. This is true, but that information is always
+available via the GitHub UI (or via the little-known <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally">refs/pull
+namespace</a>).</p>
+<p>Of course, in reality, things are more complicated than that. We have release
+branches as well as <code>develop</code> and <code>master</code>, and we deliberately merge changes
+between them. Bugs often slip through and have to be fixed later. That's all
+fine: this not a cast-iron rule which must be obeyed, but an ideal to aim
+towards.</p>
+<h2 id="merges-squashes-rebases-wtf"><a class="header" href="#merges-squashes-rebases-wtf">Merges, squashes, rebases: wtf?</a></h2>
+<p>Ok, so that's what we'd like to achieve. How do we achieve it?</p>
+<p>The TL;DR is: when you come to merge a pull request, you <em>probably</em> want to
+“squash and merge”:</p>
+<p><img src="development/img/git/squash.png" alt="squash and merge" />.</p>
+<p>(This applies whether you are merging your own PR, or that of another
+contributor.)</p>
+<p>“Squash and merge”<sup id="a1"><a href="development/git.html#f1">1</a></sup> takes all of the changes in the
+PR, and bundles them into a single commit. GitHub gives you the opportunity to
+edit the commit message before you confirm, and normally you should do so,
+because the default will be useless (again: <code>* woops typo</code> is not a useful
+thing to keep in the historical record).</p>
+<p>The main problem with this approach comes when you have a series of pull
+requests which build on top of one another: as soon as you squash-merge the
+first PR, you'll end up with a stack of conflicts to resolve in all of the
+others. In general, it's best to avoid this situation in the first place by
+trying not to have multiple related PRs in flight at the same time. Still,
+sometimes that's not possible and doing a regular merge is the lesser evil.</p>
+<p>Another occasion in which a regular merge makes more sense is a PR where you've
+deliberately created a series of commits each of which makes sense in its own
+right. For example: <a href="https://github.com/matrix-org/synapse/pull/6837">a PR which gradually propagates a refactoring operation
+through the codebase</a>, or <a href="https://github.com/matrix-org/synapse/pull/5987">a
+PR which is the culmination of several other
+PRs</a>. In this case the ability
+to figure out when a particular change/bug was introduced could be very useful.</p>
+<p>Ultimately: <strong>this is not a hard-and-fast-rule</strong>. If in doubt, ask yourself “do
+each of the commits I am about to merge make sense in their own right”, but
+remember that we're just doing our best to balance “keeping the commit history
+clean” with other factors.</p>
+<h2 id="git-branching-model"><a class="header" href="#git-branching-model">Git branching model</a></h2>
+<p>A <a href="https://nvie.com/posts/a-successful-git-branching-model/">lot</a>
+<a href="http://scottchacon.com/2011/08/31/github-flow.html">of</a>
+<a href="https://www.endoflineblog.com/gitflow-considered-harmful">words</a> have been
+written in the past about git branching models (no really, <a href="https://martinfowler.com/articles/branching-patterns.html">a
+lot</a>). I tend to
+think the whole thing is overblown. Fundamentally, it's not that
+complicated. Here's how we do it.</p>
+<p>Let's start with a picture:</p>
+<p><img src="development/img/git/branches.jpg" alt="branching model" /></p>
+<p>It looks complicated, but it's really not. There's one basic rule: <em>anyone</em> is
+free to merge from <em>any</em> more-stable branch to <em>any</em> less-stable branch at
+<em>any</em> time<sup id="a2"><a href="development/git.html#f2">2</a></sup>. (The principle behind this is that if a
+change is good enough for the more-stable branch, then it's also good enough go
+put in a less-stable branch.)</p>
+<p>Meanwhile, merging (or squashing, as per the above) from a less-stable to a
+more-stable branch is a deliberate action in which you want to publish a change
+or a set of changes to (some subset of) the world: for example, this happens
+when a PR is landed, or as part of our release process.</p>
+<p>So, what counts as a more- or less-stable branch? A little reflection will show
+that our active branches are ordered thus, from more-stable to less-stable:</p>
+<ul>
+<li><code>master</code> (tracks our last release).</li>
+<li><code>release-vX.Y</code> (the branch where we prepare the next release)<sup
+   id="a3"><a href="development/git.html#f3">3</a></sup>.</li>
+<li>PR branches which are targeting the release.</li>
+<li><code>develop</code> (our &quot;mainline&quot; branch containing our bleeding-edge).</li>
+<li>regular PR branches.</li>
+</ul>
+<p>The corollary is: if you have a bugfix that needs to land in both
+<code>release-vX.Y</code> <em>and</em> <code>develop</code>, then you should base your PR on
+<code>release-vX.Y</code>, get it merged there, and then merge from <code>release-vX.Y</code> to
+<code>develop</code>. (If a fix lands in <code>develop</code> and we later need it in a
+release-branch, we can of course cherry-pick it, but landing it in the release
+branch first helps reduce the chance of annoying conflicts.)</p>
+<hr />
+<p><b id="f1">[1]</b>: “Squash and merge” is GitHub's term for this
+operation. Given that there is no merge involved, I'm not convinced it's the
+most intuitive name. <a href="development/git.html#a1">^</a></p>
+<p><b id="f2">[2]</b>: Well, anyone with commit access.<a href="development/git.html#a2">^</a></p>
+<p><b id="f3">[3]</b>: Very, very occasionally (I think this has happened once in
+the history of Synapse), we've had two releases in flight at once. Obviously,
+<code>release-v1.2</code> is more-stable than <code>release-v1.3</code>. <a href="development/git.html#a3">^</a></p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="synapse-demo-setup"><a class="header" href="#synapse-demo-setup">Synapse demo setup</a></h1>
+<p><strong>DO NOT USE THESE DEMO SERVERS IN PRODUCTION</strong></p>
+<p>Requires you to have a <a href="https://matrix-org.github.io/synapse/develop/development/contributing_guide.html#4-install-the-dependencies">Synapse development environment setup</a>.</p>
+<p>The demo setup allows running three federation Synapse servers, with server
+names <code>localhost:8080</code>, <code>localhost:8081</code>, and <code>localhost:8082</code>.</p>
+<p>You can access them via any Matrix client over HTTP at <code>localhost:8080</code>,
+<code>localhost:8081</code>, and <code>localhost:8082</code> or over HTTPS at <code>localhost:8480</code>,
+<code>localhost:8481</code>, and <code>localhost:8482</code>.</p>
+<p>To enable the servers to communicate, self-signed SSL certificates are generated
+and the servers are configured in a highly insecure way, including:</p>
+<ul>
+<li>Not checking certificates over federation.</li>
+<li>Not verifying keys.</li>
+</ul>
+<p>The servers are configured to store their data under <code>demo/8080</code>, <code>demo/8081</code>, and
+<code>demo/8082</code>. This includes configuration, logs, SQLite databases, and media.</p>
+<p>Note that when joining a public room on a different HS via &quot;#foo:bar.net&quot;, then
+you are (in the current impl) joining a room with room_id &quot;foo&quot;. This means that
+it won't work if your HS already has a room with that name.</p>
+<h2 id="using-the-demo-scripts"><a class="header" href="#using-the-demo-scripts">Using the demo scripts</a></h2>
+<p>There's three main scripts with straightforward purposes:</p>
+<ul>
+<li><code>start.sh</code> will start the Synapse servers, generating any missing configuration.
+<ul>
+<li>This accepts a single parameter <code>--no-rate-limit</code> to &quot;disable&quot; rate limits
+(they actually still exist, but are very high).</li>
+</ul>
+</li>
+<li><code>stop.sh</code> will stop the Synapse servers.</li>
+<li><code>clean.sh</code> will delete the configuration, databases, log files, etc.</li>
+</ul>
+<p>To start a completely new set of servers, run:</p>
+<pre><code class="language-sh">./demo/stop.sh; ./demo/clean.sh &amp;&amp; ./demo/start.sh
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="opentracing"><a class="header" href="#opentracing">OpenTracing</a></h1>
+<h2 id="background"><a class="header" href="#background">Background</a></h2>
+<p>OpenTracing is a semi-standard being adopted by a number of distributed
+tracing platforms. It is a common api for facilitating vendor-agnostic
+tracing instrumentation. That is, we can use the OpenTracing api and
+select one of a number of tracer implementations to do the heavy lifting
+in the background. Our current selected implementation is Jaeger.</p>
+<p>OpenTracing is a tool which gives an insight into the causal
+relationship of work done in and between servers. The servers each track
+events and report them to a centralised server - in Synapse's case:
+Jaeger. The basic unit used to represent events is the span. The span
+roughly represents a single piece of work that was done and the time at
+which it occurred. A span can have child spans, meaning that the work of
+the child had to be completed for the parent span to complete, or it can
+have follow-on spans which represent work that is undertaken as a result
+of the parent but is not depended on by the parent to in order to
+finish.</p>
+<p>Since this is undertaken in a distributed environment a request to
+another server, such as an RPC or a simple GET, can be considered a span
+(a unit or work) for the local server. This causal link is what
+OpenTracing aims to capture and visualise. In order to do this metadata
+about the local server's span, i.e the 'span context', needs to be
+included with the request to the remote.</p>
+<p>It is up to the remote server to decide what it does with the spans it
+creates. This is called the sampling policy and it can be configured
+through Jaeger's settings.</p>
+<p>For OpenTracing concepts see
+<a href="https://opentracing.io/docs/overview/what-is-tracing/">https://opentracing.io/docs/overview/what-is-tracing/</a>.</p>
+<p>For more information about Jaeger's implementation see
+<a href="https://www.jaegertracing.io/docs/">https://www.jaegertracing.io/docs/</a></p>
+<h2 id="setting-up-opentracing"><a class="header" href="#setting-up-opentracing">Setting up OpenTracing</a></h2>
+<p>To receive OpenTracing spans, start up a Jaeger server. This can be done
+using docker like so:</p>
+<pre><code class="language-sh">docker run -d --name jaeger \
+  -p 6831:6831/udp \
+  -p 6832:6832/udp \
+  -p 5778:5778 \
+  -p 16686:16686 \
+  -p 14268:14268 \
+  jaegertracing/all-in-one:1
+</code></pre>
+<p>Latest documentation is probably at
+https://www.jaegertracing.io/docs/latest/getting-started.</p>
+<h2 id="enable-opentracing-in-synapse"><a class="header" href="#enable-opentracing-in-synapse">Enable OpenTracing in Synapse</a></h2>
+<p>OpenTracing is not enabled by default. It must be enabled in the
+homeserver config by uncommenting the config options under <code>opentracing</code>
+as shown in the <a href="./sample_config.yaml">sample config</a>. For example:</p>
+<pre><code class="language-yaml">opentracing:
+  enabled: true
+  homeserver_whitelist:
+    - &quot;mytrustedhomeserver.org&quot;
+    - &quot;*.myotherhomeservers.com&quot;
+</code></pre>
+<h2 id="homeserver-whitelisting"><a class="header" href="#homeserver-whitelisting">Homeserver whitelisting</a></h2>
+<p>The homeserver whitelist is configured using regular expressions. A list
+of regular expressions can be given and their union will be compared
+when propagating any spans contexts to another homeserver.</p>
+<p>Though it's mostly safe to send and receive span contexts to and from
+untrusted users since span contexts are usually opaque ids it can lead
+to two problems, namely:</p>
+<ul>
+<li>If the span context is marked as sampled by the sending homeserver
+the receiver will sample it. Therefore two homeservers with wildly
+different sampling policies could incur higher sampling counts than
+intended.</li>
+<li>Sending servers can attach arbitrary data to spans, known as
+'baggage'. For safety this has been disabled in Synapse but that
+doesn't prevent another server sending you baggage which will be
+logged to OpenTracing's logs.</li>
+</ul>
+<h2 id="configuring-jaeger"><a class="header" href="#configuring-jaeger">Configuring Jaeger</a></h2>
+<p>Sampling strategies can be set as in this document:
+<a href="https://www.jaegertracing.io/docs/latest/sampling/">https://www.jaegertracing.io/docs/latest/sampling/</a>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="synapse-database-schema-files"><a class="header" href="#synapse-database-schema-files">Synapse database schema files</a></h1>
+<p>Synapse's database schema is stored in the <code>synapse.storage.schema</code> module.</p>
+<h2 id="logical-databases"><a class="header" href="#logical-databases">Logical databases</a></h2>
+<p>Synapse supports splitting its datastore across multiple physical databases (which can
+be useful for large installations), and the schema files are therefore split according
+to the logical database they apply to.</p>
+<p>At the time of writing, the following &quot;logical&quot; databases are supported:</p>
+<ul>
+<li><code>state</code> - used to store Matrix room state (more specifically, <code>state_groups</code>,
+their relationships and contents).</li>
+<li><code>main</code> - stores everything else.</li>
+</ul>
+<p>Additionally, the <code>common</code> directory contains schema files for tables which must be
+present on <em>all</em> physical databases.</p>
+<h2 id="synapse-schema-versions"><a class="header" href="#synapse-schema-versions">Synapse schema versions</a></h2>
+<p>Synapse manages its database schema via &quot;schema versions&quot;. These are mainly used to
+help avoid confusion if the Synapse codebase is rolled back after the database is
+updated. They work as follows:</p>
+<ul>
+<li>
+<p>The Synapse codebase defines a constant <code>synapse.storage.schema.SCHEMA_VERSION</code>
+which represents the expectations made about the database by that version. For
+example, as of Synapse v1.36, this is <code>59</code>.</p>
+</li>
+<li>
+<p>The database stores a &quot;compatibility version&quot; in
+<code>schema_compat_version.compat_version</code> which defines the <code>SCHEMA_VERSION</code> of the
+oldest version of Synapse which will work with the database. On startup, if
+<code>compat_version</code> is found to be newer than <code>SCHEMA_VERSION</code>, Synapse will refuse to
+start.</p>
+<p>Synapse automatically updates this field from
+<code>synapse.storage.schema.SCHEMA_COMPAT_VERSION</code>.</p>
+</li>
+<li>
+<p>Whenever a backwards-incompatible change is made to the database format (normally
+via a <code>delta</code> file), <code>synapse.storage.schema.SCHEMA_COMPAT_VERSION</code> is also updated
+so that administrators can not accidentally roll back to a too-old version of Synapse.</p>
+</li>
+</ul>
+<p>Generally, the goal is to maintain compatibility with at least one or two previous
+releases of Synapse, so any substantial change tends to require multiple releases and a
+bit of forward-planning to get right.</p>
+<p>As a worked example: we want to remove the <code>room_stats_historical</code> table. Here is how it
+might pan out.</p>
+<ol>
+<li>
+<p>Replace any code that <em>reads</em> from <code>room_stats_historical</code> with alternative
+implementations, but keep writing to it in case of rollback to an earlier version.
+Also, increase <code>synapse.storage.schema.SCHEMA_VERSION</code>.  In this
+instance, there is no existing code which reads from <code>room_stats_historical</code>, so
+our starting point is:</p>
+<p>v1.36.0: <code>SCHEMA_VERSION=59</code>, <code>SCHEMA_COMPAT_VERSION=59</code></p>
+</li>
+<li>
+<p>Next (say in Synapse v1.37.0): remove the code that <em>writes</em> to
+<code>room_stats_historical</code>, but don’t yet remove the table in case of rollback to
+v1.36.0. Again, we increase <code>synapse.storage.schema.SCHEMA_VERSION</code>, but
+because we have not broken compatibility with v1.36, we do not yet update
+<code>SCHEMA_COMPAT_VERSION</code>. We now have:</p>
+<p>v1.37.0: <code>SCHEMA_VERSION=60</code>, <code>SCHEMA_COMPAT_VERSION=59</code>.</p>
+</li>
+<li>
+<p>Later (say in Synapse v1.38.0): we can remove the table altogether. This will
+break compatibility with v1.36.0, so we must update <code>SCHEMA_COMPAT_VERSION</code> accordingly.
+There is no need to update <code>synapse.storage.schema.SCHEMA_VERSION</code>, since there is no
+change to the Synapse codebase here. So we end up with:</p>
+<p>v1.38.0: <code>SCHEMA_VERSION=60</code>, <code>SCHEMA_COMPAT_VERSION=60</code>.</p>
+</li>
+</ol>
+<p>If in doubt about whether to update <code>SCHEMA_VERSION</code> or not, it is generally best to
+lean towards doing so.</p>
+<h2 id="full-schema-dumps"><a class="header" href="#full-schema-dumps">Full schema dumps</a></h2>
+<p>In the <code>full_schemas</code> directories, only the most recently-numbered snapshot is used
+(<code>54</code> at the time of writing). Older snapshots (eg, <code>16</code>) are present for historical
+reference only.</p>
+<h3 id="building-full-schema-dumps"><a class="header" href="#building-full-schema-dumps">Building full schema dumps</a></h3>
+<p>If you want to recreate these schemas, they need to be made from a database that
+has had all background updates run.</p>
+<p>To do so, use <code>scripts-dev/make_full_schema.sh</code>. This will produce new
+<code>full.sql.postgres</code> and <code>full.sql.sqlite</code> files.</p>
+<p>Ensure postgres is installed, then run:</p>
+<pre><code class="language-sh">./scripts-dev/make_full_schema.sh -p postgres_username -o output_dir/
+</code></pre>
+<p>NB at the time of writing, this script predates the split into separate <code>state</code>/<code>main</code>
+databases so will require updates to handle that correctly.</p>
+<h2 id="delta-files"><a class="header" href="#delta-files">Delta files</a></h2>
+<p>Delta files define the steps required to upgrade the database from an earlier version.
+They can be written as either a file containing a series of SQL statements, or a Python
+module.</p>
+<p>Synapse remembers which delta files it has applied to a database (they are stored in the
+<code>applied_schema_deltas</code> table) and will not re-apply them (even if a given file is
+subsequently updated).</p>
+<p>Delta files should be placed in a directory named <code>synapse/storage/schema/&lt;database&gt;/delta/&lt;version&gt;/</code>.
+They are applied in alphanumeric order, so  by convention the first two characters
+of the filename should be an integer such as <code>01</code>, to put the file in the right order.</p>
+<h3 id="sql-delta-files"><a class="header" href="#sql-delta-files">SQL delta files</a></h3>
+<p>These should be named <code>*.sql</code>, or —  for changes which should only be applied for a
+given database engine — <code>*.sql.posgres</code> or <code>*.sql.sqlite</code>. For example, a delta which
+adds a new column to the <code>foo</code> table might be called <code>01add_bar_to_foo.sql</code>.</p>
+<p>Note that our SQL parser is a bit simple - it understands comments (<code>--</code> and <code>/*...*/</code>),
+but complex statements which require a <code>;</code> in the middle of them (such as <code>CREATE TRIGGER</code>) are beyond it and you'll have to use a Python delta file.</p>
+<h3 id="python-delta-files"><a class="header" href="#python-delta-files">Python delta files</a></h3>
+<p>For more flexibility, a delta file can take the form of a python module. These should
+be named <code>*.py</code>. Note that database-engine-specific modules are not supported here –
+instead you can write <code>if isinstance(database_engine, PostgresEngine)</code> or similar.</p>
+<p>A Python delta module should define either or both of the following functions:</p>
+<pre><code class="language-python">import synapse.config.homeserver
+import synapse.storage.engines
+import synapse.storage.types
+
+
+def run_create(
+    cur: synapse.storage.types.Cursor,
+    database_engine: synapse.storage.engines.BaseDatabaseEngine,
+) -&gt; None:
+    &quot;&quot;&quot;Called whenever an existing or new database is to be upgraded&quot;&quot;&quot;
+    ...
+
+def run_upgrade(
+    cur: synapse.storage.types.Cursor,
+    database_engine: synapse.storage.engines.BaseDatabaseEngine,
+    config: synapse.config.homeserver.HomeServerConfig,
+) -&gt; None:
+    &quot;&quot;&quot;Called whenever an existing database is to be upgraded.&quot;&quot;&quot;
+    ...
+</code></pre>
+<h2 id="boolean-columns"><a class="header" href="#boolean-columns">Boolean columns</a></h2>
+<p>Boolean columns require special treatment, since SQLite treats booleans the
+same as integers.</p>
+<p>There are three separate aspects to this:</p>
+<ul>
+<li>
+<p>Any new boolean column must be added to the <code>BOOLEAN_COLUMNS</code> list in
+<code>synapse/_scripts/synapse_port_db.py</code>. This tells the port script to cast
+the integer value from SQLite to a boolean before writing the value to the
+postgres database.</p>
+</li>
+<li>
+<p>Before SQLite 3.23, <code>TRUE</code> and <code>FALSE</code> were not recognised as constants by
+SQLite, and the <code>IS [NOT] TRUE</code>/<code>IS [NOT] FALSE</code> operators were not
+supported. This makes it necessary to avoid using <code>TRUE</code> and <code>FALSE</code>
+constants in SQL commands.</p>
+<p>For example, to insert a <code>TRUE</code> value into the database, write:</p>
+<pre><code class="language-python">txn.execute(&quot;INSERT INTO tbl(col) VALUES (?)&quot;, (True, ))
+</code></pre>
+</li>
+<li>
+<p>Default values for new boolean columns present a particular
+difficulty. Generally it is best to create separate schema files for
+Postgres and SQLite. For example:</p>
+<pre><code class="language-sql"># in 00delta.sql.postgres:
+ALTER TABLE tbl ADD COLUMN col BOOLEAN DEFAULT FALSE;
+</code></pre>
+<pre><code class="language-sql"># in 00delta.sql.sqlite:
+ALTER TABLE tbl ADD COLUMN col BOOLEAN DEFAULT 0;
+</code></pre>
+<p>Note that there is a particularly insidious failure mode here: the Postgres
+flavour will be accepted by SQLite 3.22, but will give a column whose
+default value is the <strong>string</strong> <code>&quot;FALSE&quot;</code> - which, when cast back to a boolean
+in Python, evaluates to <code>True</code>.</p>
+</li>
+</ul>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="implementing-experimental-features-in-synapse"><a class="header" href="#implementing-experimental-features-in-synapse">Implementing experimental features in Synapse</a></h1>
+<p>It can be desirable to implement &quot;experimental&quot; features which are disabled by
+default and must be explicitly enabled via the Synapse configuration. This is
+applicable for features which:</p>
+<ul>
+<li>Are unstable in the Matrix spec (e.g. those defined by an MSC that has not yet been merged).</li>
+<li>Developers are not confident in their use by general Synapse administrators/users
+(e.g. a feature is incomplete, buggy, performs poorly, or needs further testing).</li>
+</ul>
+<p>Note that this only really applies to features which are expected to be desirable
+to a broad audience. The <a href="development/../modules/index.html">module infrastructure</a> should
+instead be investigated for non-standard features.</p>
+<p>Guarding experimental features behind configuration flags should help with some
+of the following scenarios:</p>
+<ul>
+<li>Ensure that clients do not assume that unstable features exist (failing
+gracefully if they do not).</li>
+<li>Unstable features do not become de-facto standards and can be removed
+aggressively (since only those who have opted-in will be affected).</li>
+<li>Ease finding the implementation of unstable features in Synapse (for future
+removal or stabilization).</li>
+<li>Ease testing a feature (or removal of feature) due to enabling/disabling without
+code changes. It also becomes possible to ask for wider testing, if desired.</li>
+</ul>
+<p>Experimental configuration flags should be disabled by default (requiring Synapse
+administrators to explicitly opt-in), although there are situations where it makes
+sense (from a product point-of-view) to enable features by default. This is
+expected and not an issue.</p>
+<p>It is not a requirement for experimental features to be behind a configuration flag,
+but one should be used if unsure.</p>
+<p>New experimental configuration flags should be added under the <code>experimental</code>
+configuration key (see the <code>synapse.config.experimental</code> file) and either explain
+(briefly) what is being enabled, or include the MSC number.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="log-contexts"><a class="header" href="#log-contexts">Log Contexts</a></h1>
+<p>To help track the processing of individual requests, synapse uses a
+'<code>log context</code>' to track which request it is handling at any given
+moment. This is done via a thread-local variable; a <code>logging.Filter</code> is
+then used to fish the information back out of the thread-local variable
+and add it to each log record.</p>
+<p>Logcontexts are also used for CPU and database accounting, so that we
+can track which requests were responsible for high CPU use or database
+activity.</p>
+<p>The <code>synapse.logging.context</code> module provides facilities for managing
+the current log context (as well as providing the <code>LoggingContextFilter</code>
+class).</p>
+<p>Asynchronous functions make the whole thing complicated, so this document describes
+how it all works, and how to write code which follows the rules.</p>
+<p>In this document, &quot;awaitable&quot; refers to any object which can be <code>await</code>ed. In the context of
+Synapse, that normally means either a coroutine or a Twisted 
+<a href="https://twistedmatrix.com/documents/current/api/twisted.internet.defer.Deferred.html"><code>Deferred</code></a>.</p>
+<h2 id="logcontexts-without-asynchronous-code"><a class="header" href="#logcontexts-without-asynchronous-code">Logcontexts without asynchronous code</a></h2>
+<p>In the absence of any asynchronous voodoo, things are simple enough. As with
+any code of this nature, the rule is that our function should leave
+things as it found them:</p>
+<pre><code class="language-python">from synapse.logging import context         # omitted from future snippets
+
+def handle_request(request_id):
+    request_context = context.LoggingContext()
+
+    calling_context = context.set_current_context(request_context)
+    try:
+        request_context.request = request_id
+        do_request_handling()
+        logger.debug(&quot;finished&quot;)
+    finally:
+        context.set_current_context(calling_context)
+
+def do_request_handling():
+    logger.debug(&quot;phew&quot;)  # this will be logged against request_id
+</code></pre>
+<p>LoggingContext implements the context management methods, so the above
+can be written much more succinctly as:</p>
+<pre><code class="language-python">def handle_request(request_id):
+    with context.LoggingContext() as request_context:
+        request_context.request = request_id
+        do_request_handling()
+        logger.debug(&quot;finished&quot;)
+
+def do_request_handling():
+    logger.debug(&quot;phew&quot;)
+</code></pre>
+<h2 id="using-logcontexts-with-awaitables"><a class="header" href="#using-logcontexts-with-awaitables">Using logcontexts with awaitables</a></h2>
+<p>Awaitables break the linear flow of code so that there is no longer a single entry point
+where we should set the logcontext and a single exit point where we should remove it.</p>
+<p>Consider the example above, where <code>do_request_handling</code> needs to do some
+blocking operation, and returns an awaitable:</p>
+<pre><code class="language-python">async def handle_request(request_id):
+    with context.LoggingContext() as request_context:
+        request_context.request = request_id
+        await do_request_handling()
+        logger.debug(&quot;finished&quot;)
+</code></pre>
+<p>In the above flow:</p>
+<ul>
+<li>The logcontext is set</li>
+<li><code>do_request_handling</code> is called, and returns an awaitable</li>
+<li><code>handle_request</code> awaits the awaitable</li>
+<li>Execution of <code>handle_request</code> is suspended</li>
+</ul>
+<p>So we have stopped processing the request (and will probably go on to
+start processing the next), without clearing the logcontext.</p>
+<p>To circumvent this problem, synapse code assumes that, wherever you have
+an awaitable, you will want to <code>await</code> it. To that end, whereever
+functions return awaitables, we adopt the following conventions:</p>
+<p><strong>Rules for functions returning awaitables:</strong></p>
+<blockquote>
+<ul>
+<li>If the awaitable is already complete, the function returns with the
+same logcontext it started with.</li>
+<li>If the awaitable is incomplete, the function clears the logcontext
+before returning; when the awaitable completes, it restores the
+logcontext before running any callbacks.</li>
+</ul>
+</blockquote>
+<p>That sounds complicated, but actually it means a lot of code (including
+the example above) &quot;just works&quot;. There are two cases:</p>
+<ul>
+<li>
+<p>If <code>do_request_handling</code> returns a completed awaitable, then the
+logcontext will still be in place. In this case, execution will
+continue immediately after the <code>await</code>; the &quot;finished&quot; line will
+be logged against the right context, and the <code>with</code> block restores
+the original context before we return to the caller.</p>
+</li>
+<li>
+<p>If the returned awaitable is incomplete, <code>do_request_handling</code> clears
+the logcontext before returning. The logcontext is therefore clear
+when <code>handle_request</code> <code>await</code>s the awaitable.</p>
+<p>Once <code>do_request_handling</code>'s awaitable completes, it will reinstate
+the logcontext, before running the second half of <code>handle_request</code>,
+so again the &quot;finished&quot; line will be logged against the right context,
+and the <code>with</code> block restores the original context.</p>
+</li>
+</ul>
+<p>As an aside, it's worth noting that <code>handle_request</code> follows our rules</p>
+<ul>
+<li>though that only matters if the caller has its own logcontext which it
+cares about.</li>
+</ul>
+<p>The following sections describe pitfalls and helpful patterns when
+implementing these rules.</p>
+<h2 id="always-await-your-awaitables"><a class="header" href="#always-await-your-awaitables">Always await your awaitables</a></h2>
+<p>Whenever you get an awaitable back from a function, you should <code>await</code> on
+it as soon as possible. Do not pass go; do not do any logging; do not
+call any other functions.</p>
+<pre><code class="language-python">async def fun():
+    logger.debug(&quot;starting&quot;)
+    await do_some_stuff()       # just like this
+
+    coro = more_stuff()
+    result = await coro         # also fine, of course
+
+    return result
+</code></pre>
+<p>Provided this pattern is followed all the way back up to the callchain
+to where the logcontext was set, this will make things work out ok:
+provided <code>do_some_stuff</code> and <code>more_stuff</code> follow the rules above, then
+so will <code>fun</code>.</p>
+<p>It's all too easy to forget to <code>await</code>: for instance if we forgot that
+<code>do_some_stuff</code> returned an awaitable, we might plough on regardless. This
+leads to a mess; it will probably work itself out eventually, but not
+before a load of stuff has been logged against the wrong context.
+(Normally, other things will break, more obviously, if you forget to
+<code>await</code>, so this tends not to be a major problem in practice.)</p>
+<p>Of course sometimes you need to do something a bit fancier with your
+awaitable - not all code follows the linear A-then-B-then-C pattern.
+Notes on implementing more complex patterns are in later sections.</p>
+<h2 id="where-you-create-a-new-awaitable-make-it-follow-the-rules"><a class="header" href="#where-you-create-a-new-awaitable-make-it-follow-the-rules">Where you create a new awaitable, make it follow the rules</a></h2>
+<p>Most of the time, an awaitable comes from another synapse function.
+Sometimes, though, we need to make up a new awaitable, or we get an awaitable
+back from external code. We need to make it follow our rules.</p>
+<p>The easy way to do it is by using <code>context.make_deferred_yieldable</code>. Suppose we want to implement
+<code>sleep</code>, which returns a deferred which will run its callbacks after a
+given number of seconds. That might look like:</p>
+<pre><code class="language-python"># not a logcontext-rules-compliant function
+def get_sleep_deferred(seconds):
+    d = defer.Deferred()
+    reactor.callLater(seconds, d.callback, None)
+    return d
+</code></pre>
+<p>That doesn't follow the rules, but we can fix it by calling it through
+<code>context.make_deferred_yieldable</code>:</p>
+<pre><code class="language-python">async def sleep(seconds):
+    return await context.make_deferred_yieldable(get_sleep_deferred(seconds))
+</code></pre>
+<h2 id="fire-and-forget"><a class="header" href="#fire-and-forget">Fire-and-forget</a></h2>
+<p>Sometimes you want to fire off a chain of execution, but not wait for
+its result. That might look a bit like this:</p>
+<pre><code class="language-python">async def do_request_handling():
+    await foreground_operation()
+
+    # *don't* do this
+    background_operation()
+
+    logger.debug(&quot;Request handling complete&quot;)
+
+async def background_operation():
+    await first_background_step()
+    logger.debug(&quot;Completed first step&quot;)
+    await second_background_step()
+    logger.debug(&quot;Completed second step&quot;)
+</code></pre>
+<p>The above code does a couple of steps in the background after
+<code>do_request_handling</code> has finished. The log lines are still logged
+against the <code>request_context</code> logcontext, which may or may not be
+desirable. There are two big problems with the above, however. The first
+problem is that, if <code>background_operation</code> returns an incomplete
+awaitable, it will expect its caller to <code>await</code> immediately, so will have
+cleared the logcontext. In this example, that means that 'Request
+handling complete' will be logged without any context.</p>
+<p>The second problem, which is potentially even worse, is that when the
+awaitable returned by <code>background_operation</code> completes, it will restore
+the original logcontext. There is nothing waiting on that awaitable, so
+the logcontext will leak into the reactor and possibly get attached to
+some arbitrary future operation.</p>
+<p>There are two potential solutions to this.</p>
+<p>One option is to surround the call to <code>background_operation</code> with a
+<code>PreserveLoggingContext</code> call. That will reset the logcontext before
+starting <code>background_operation</code> (so the context restored when the
+deferred completes will be the empty logcontext), and will restore the
+current logcontext before continuing the foreground process:</p>
+<pre><code class="language-python">async def do_request_handling():
+    await foreground_operation()
+
+    # start background_operation off in the empty logcontext, to
+    # avoid leaking the current context into the reactor.
+    with PreserveLoggingContext():
+        background_operation()
+
+    # this will now be logged against the request context
+    logger.debug(&quot;Request handling complete&quot;)
+</code></pre>
+<p>Obviously that option means that the operations done in
+<code>background_operation</code> would be not be logged against a logcontext
+(though that might be fixed by setting a different logcontext via a
+<code>with LoggingContext(...)</code> in <code>background_operation</code>).</p>
+<p>The second option is to use <code>context.run_in_background</code>, which wraps a
+function so that it doesn't reset the logcontext even when it returns
+an incomplete awaitable, and adds a callback to the returned awaitable to
+reset the logcontext. In other words, it turns a function that follows
+the Synapse rules about logcontexts and awaitables into one which behaves
+more like an external function --- the opposite operation to that
+described in the previous section. It can be used like this:</p>
+<pre><code class="language-python">async def do_request_handling():
+    await foreground_operation()
+
+    context.run_in_background(background_operation)
+
+    # this will now be logged against the request context
+    logger.debug(&quot;Request handling complete&quot;)
+</code></pre>
+<h2 id="passing-synapse-deferreds-into-third-party-functions"><a class="header" href="#passing-synapse-deferreds-into-third-party-functions">Passing synapse deferreds into third-party functions</a></h2>
+<p>A typical example of this is where we want to collect together two or
+more awaitables via <code>defer.gatherResults</code>:</p>
+<pre><code class="language-python">a1 = operation1()
+a2 = operation2()
+a3 = defer.gatherResults([a1, a2])
+</code></pre>
+<p>This is really a variation of the fire-and-forget problem above, in that
+we are firing off <code>a1</code> and <code>a2</code> without awaiting on them. The difference
+is that we now have third-party code attached to their callbacks. Anyway
+either technique given in the <a href="log_contexts.html#fire-and-forget">Fire-and-forget</a>
+section will work.</p>
+<p>Of course, the new awaitable returned by <code>gather</code> needs to be
+wrapped in order to make it follow the logcontext rules before we can
+yield it, as described in <a href="log_contexts.html#where-you-create-a-new-awaitable-make-it-follow-the-rules">Where you create a new awaitable, make it
+follow the
+rules</a>.</p>
+<p>So, option one: reset the logcontext before starting the operations to
+be gathered:</p>
+<pre><code class="language-python">async def do_request_handling():
+    with PreserveLoggingContext():
+        a1 = operation1()
+        a2 = operation2()
+        result = await defer.gatherResults([a1, a2])
+</code></pre>
+<p>In this case particularly, though, option two, of using
+<code>context.run_in_background</code> almost certainly makes more sense, so that
+<code>operation1</code> and <code>operation2</code> are both logged against the original
+logcontext. This looks like:</p>
+<pre><code class="language-python">async def do_request_handling():
+    a1 = context.run_in_background(operation1)
+    a2 = context.run_in_background(operation2)
+
+    result = await make_deferred_yieldable(defer.gatherResults([a1, a2]))
+</code></pre>
+<h2 id="a-note-on-garbage-collection-of-awaitable-chains"><a class="header" href="#a-note-on-garbage-collection-of-awaitable-chains">A note on garbage-collection of awaitable chains</a></h2>
+<p>It turns out that our logcontext rules do not play nicely with awaitable
+chains which get orphaned and garbage-collected.</p>
+<p>Imagine we have some code that looks like this:</p>
+<pre><code class="language-python">listener_queue = []
+
+def on_something_interesting():
+    for d in listener_queue:
+        d.callback(&quot;foo&quot;)
+
+async def await_something_interesting():
+    new_awaitable = defer.Deferred()
+    listener_queue.append(new_awaitable)
+
+    with PreserveLoggingContext():
+        await new_awaitable
+</code></pre>
+<p>Obviously, the idea here is that we have a bunch of things which are
+waiting for an event. (It's just an example of the problem here, but a
+relatively common one.)</p>
+<p>Now let's imagine two further things happen. First of all, whatever was
+waiting for the interesting thing goes away. (Perhaps the request times
+out, or something <em>even more</em> interesting happens.)</p>
+<p>Secondly, let's suppose that we decide that the interesting thing is
+never going to happen, and we reset the listener queue:</p>
+<pre><code class="language-python">def reset_listener_queue():
+    listener_queue.clear()
+</code></pre>
+<p>So, both ends of the awaitable chain have now dropped their references,
+and the awaitable chain is now orphaned, and will be garbage-collected at
+some point. Note that <code>await_something_interesting</code> is a coroutine, 
+which Python implements as a generator function.  When Python
+garbage-collects generator functions, it gives them a chance to 
+clean up by making the <code>await</code> (or <code>yield</code>) raise a <code>GeneratorExit</code>
+exception. In our case, that means that the <code>__exit__</code> handler of
+<code>PreserveLoggingContext</code> will carefully restore the request context, but
+there is now nothing waiting for its return, so the request context is
+never cleared.</p>
+<p>To reiterate, this problem only arises when <em>both</em> ends of a awaitable
+chain are dropped. Dropping the the reference to an awaitable you're
+supposed to be awaiting is bad practice, so this doesn't
+actually happen too much. Unfortunately, when it does happen, it will
+lead to leaked logcontexts which are incredibly hard to track down.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="replication-architecture"><a class="header" href="#replication-architecture">Replication Architecture</a></h1>
+<h2 id="motivation"><a class="header" href="#motivation">Motivation</a></h2>
+<p>We'd like to be able to split some of the work that synapse does into
+multiple python processes. In theory multiple synapse processes could
+share a single postgresql database and we'd scale up by running more
+synapse processes. However much of synapse assumes that only one process
+is interacting with the database, both for assigning unique identifiers
+when inserting into tables, notifying components about new updates, and
+for invalidating its caches.</p>
+<p>So running multiple copies of the current code isn't an option. One way
+to run multiple processes would be to have a single writer process and
+multiple reader processes connected to the same database. In order to do
+this we'd need a way for the reader process to invalidate its in-memory
+caches when an update happens on the writer. One way to do this is for
+the writer to present an append-only log of updates which the readers
+can consume to invalidate their caches and to push updates to listening
+clients or pushers.</p>
+<p>Synapse already stores much of its data as an append-only log so that it
+can correctly respond to <code>/sync</code> requests so the amount of code changes
+needed to expose the append-only log to the readers should be fairly
+minimal.</p>
+<h2 id="architecture"><a class="header" href="#architecture">Architecture</a></h2>
+<h3 id="the-replication-protocol"><a class="header" href="#the-replication-protocol">The Replication Protocol</a></h3>
+<p>See <a href="tcp_replication.html">the TCP replication documentation</a>.</p>
+<h3 id="the-slaved-datastore"><a class="header" href="#the-slaved-datastore">The Slaved DataStore</a></h3>
+<p>There are read-only version of the synapse storage layer in
+<code>synapse/replication/slave/storage</code> that use the response of the
+replication API to invalidate their caches.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="tcp-replication"><a class="header" href="#tcp-replication">TCP Replication</a></h1>
+<h2 id="motivation-1"><a class="header" href="#motivation-1">Motivation</a></h2>
+<p>Previously the workers used an HTTP long poll mechanism to get updates
+from the master, which had the problem of causing a lot of duplicate
+work on the server. This TCP protocol replaces those APIs with the aim
+of increased efficiency.</p>
+<h2 id="overview-3"><a class="header" href="#overview-3">Overview</a></h2>
+<p>The protocol is based on fire and forget, line based commands. An
+example flow would be (where '&gt;' indicates master to worker and
+'&lt;' worker to master flows):</p>
+<pre><code>&gt; SERVER example.com
+&lt; REPLICATE
+&gt; POSITION events master 53 53
+&gt; RDATA events master 54 [&quot;$foo1:bar.com&quot;, ...]
+&gt; RDATA events master 55 [&quot;$foo4:bar.com&quot;, ...]
+</code></pre>
+<p>The example shows the server accepting a new connection and sending its identity
+with the <code>SERVER</code> command, followed by the client server to respond with the
+position of all streams. The server then periodically sends <code>RDATA</code> commands
+which have the format <code>RDATA &lt;stream_name&gt; &lt;instance_name&gt; &lt;token&gt; &lt;row&gt;</code>, where
+the format of <code>&lt;row&gt;</code> is defined by the individual streams. The
+<code>&lt;instance_name&gt;</code> is the name of the Synapse process that generated the data
+(usually &quot;master&quot;).</p>
+<p>Error reporting happens by either the client or server sending an ERROR
+command, and usually the connection will be closed.</p>
+<p>Since the protocol is a simple line based, its possible to manually
+connect to the server using a tool like netcat. A few things should be
+noted when manually using the protocol:</p>
+<ul>
+<li>The federation stream is only available if federation sending has
+been disabled on the main process.</li>
+<li>The server will only time connections out that have sent a <code>PING</code>
+command. If a ping is sent then the connection will be closed if no
+further commands are receieved within 15s. Both the client and
+server protocol implementations will send an initial PING on
+connection and ensure at least one command every 5s is sent (not
+necessarily <code>PING</code>).</li>
+<li><code>RDATA</code> commands <em>usually</em> include a numeric token, however if the
+stream has multiple rows to replicate per token the server will send
+multiple <code>RDATA</code> commands, with all but the last having a token of
+<code>batch</code>. See the documentation on <code>commands.RdataCommand</code> for
+further details.</li>
+</ul>
+<h2 id="architecture-1"><a class="header" href="#architecture-1">Architecture</a></h2>
+<p>The basic structure of the protocol is line based, where the initial
+word of each line specifies the command. The rest of the line is parsed
+based on the command. For example, the RDATA command is defined as:</p>
+<pre><code>RDATA &lt;stream_name&gt; &lt;instance_name&gt; &lt;token&gt; &lt;row_json&gt;
+</code></pre>
+<p>(Note that &lt;row_json&gt; may contains spaces, but cannot contain
+newlines.)</p>
+<p>Blank lines are ignored.</p>
+<h3 id="keep-alives"><a class="header" href="#keep-alives">Keep alives</a></h3>
+<p>Both sides are expected to send at least one command every 5s or so, and
+should send a <code>PING</code> command if necessary. If either side do not receive
+a command within e.g. 15s then the connection should be closed.</p>
+<p>Because the server may be connected to manually using e.g. netcat, the
+timeouts aren't enabled until an initial <code>PING</code> command is seen. Both
+the client and server implementations below send a <code>PING</code> command
+immediately on connection to ensure the timeouts are enabled.</p>
+<p>This ensures that both sides can quickly realize if the tcp connection
+has gone and handle the situation appropriately.</p>
+<h3 id="start-up"><a class="header" href="#start-up">Start up</a></h3>
+<p>When a new connection is made, the server:</p>
+<ul>
+<li>Sends a <code>SERVER</code> command, which includes the identity of the server,
+allowing the client to detect if its connected to the expected
+server</li>
+<li>Sends a <code>PING</code> command as above, to enable the client to time out
+connections promptly.</li>
+</ul>
+<p>The client:</p>
+<ul>
+<li>Sends a <code>NAME</code> command, allowing the server to associate a human
+friendly name with the connection. This is optional.</li>
+<li>Sends a <code>PING</code> as above</li>
+<li>Sends a <code>REPLICATE</code> to get the current position of all streams.</li>
+<li>On receipt of a <code>SERVER</code> command, checks that the server name
+matches the expected server name.</li>
+</ul>
+<h3 id="error-handling"><a class="header" href="#error-handling">Error handling</a></h3>
+<p>If either side detects an error it can send an <code>ERROR</code> command and close
+the connection.</p>
+<p>If the client side loses the connection to the server it should
+reconnect, following the steps above.</p>
+<h3 id="congestion"><a class="header" href="#congestion">Congestion</a></h3>
+<p>If the server sends messages faster than the client can consume them the
+server will first buffer a (fairly large) number of commands and then
+disconnect the client. This ensures that we don't queue up an unbounded
+number of commands in memory and gives us a potential oppurtunity to
+squawk loudly. When/if the client recovers it can reconnect to the
+server and ask for missed messages.</p>
+<h3 id="reliability"><a class="header" href="#reliability">Reliability</a></h3>
+<p>In general the replication stream should be considered an unreliable
+transport since e.g. commands are not resent if the connection
+disappears.</p>
+<p>The exception to that are the replication streams, i.e. RDATA commands,
+since these include tokens which can be used to restart the stream on
+connection errors.</p>
+<p>The client should keep track of the token in the last RDATA command
+received for each stream so that on reconneciton it can start streaming
+from the correct place. Note: not all RDATA have valid tokens due to
+batching. See <code>RdataCommand</code> for more details.</p>
+<h3 id="example-4"><a class="header" href="#example-4">Example</a></h3>
+<p>An example iteraction is shown below. Each line is prefixed with '&gt;'
+or '&lt;' to indicate which side is sending, these are <em>not</em> included on
+the wire:</p>
+<pre><code>* connection established *
+&gt; SERVER localhost:8823
+&gt; PING 1490197665618
+&lt; NAME synapse.app.appservice
+&lt; PING 1490197665618
+&lt; REPLICATE
+&gt; POSITION events master 1 1
+&gt; POSITION backfill master 1 1
+&gt; POSITION caches master 1 1
+&gt; RDATA caches master 2 [&quot;get_user_by_id&quot;,[&quot;@01register-user:localhost:8823&quot;],1490197670513]
+&gt; RDATA events master 14 [&quot;$149019767112vOHxz:localhost:8823&quot;,
+    &quot;!AFDCvgApUmpdfVjIXm:localhost:8823&quot;,&quot;m.room.guest_access&quot;,&quot;&quot;,null]
+&lt; PING 1490197675618
+&gt; ERROR server stopping
+* connection closed by server *
+</code></pre>
+<p>The <code>POSITION</code> command sent by the server is used to set the clients
+position without needing to send data with the <code>RDATA</code> command.</p>
+<p>An example of a batched set of <code>RDATA</code> is:</p>
+<pre><code>&gt; RDATA caches master batch [&quot;get_user_by_id&quot;,[&quot;@test:localhost:8823&quot;],1490197670513]
+&gt; RDATA caches master batch [&quot;get_user_by_id&quot;,[&quot;@test2:localhost:8823&quot;],1490197670513]
+&gt; RDATA caches master batch [&quot;get_user_by_id&quot;,[&quot;@test3:localhost:8823&quot;],1490197670513]
+&gt; RDATA caches master 54 [&quot;get_user_by_id&quot;,[&quot;@test4:localhost:8823&quot;],1490197670513]
+</code></pre>
+<p>In this case the client shouldn't advance their caches token until it
+sees the the last <code>RDATA</code>.</p>
+<h3 id="list-of-commands"><a class="header" href="#list-of-commands">List of commands</a></h3>
+<p>The list of valid commands, with which side can send it: server (S) or
+client (C):</p>
+<h4 id="server-s"><a class="header" href="#server-s">SERVER (S)</a></h4>
+<p>Sent at the start to identify which server the client is talking to</p>
+<h4 id="rdata-s"><a class="header" href="#rdata-s">RDATA (S)</a></h4>
+<p>A single update in a stream</p>
+<h4 id="position-s"><a class="header" href="#position-s">POSITION (S)</a></h4>
+<p>On receipt of a POSITION command clients should check if they have missed any
+updates, and if so then fetch them out of band. Sent in response to a
+REPLICATE command (but can happen at any time).</p>
+<p>The POSITION command includes the source of the stream. Currently all streams
+are written by a single process (usually &quot;master&quot;). If fetching missing
+updates via HTTP API, rather than via the DB, then processes should make the
+request to the appropriate process.</p>
+<p>Two positions are included, the &quot;new&quot; position and the last position sent respectively.
+This allows servers to tell instances that the positions have advanced but no
+data has been written, without clients needlessly checking to see if they
+have missed any updates.</p>
+<h4 id="error-s-c"><a class="header" href="#error-s-c">ERROR (S, C)</a></h4>
+<p>There was an error</p>
+<h4 id="ping-s-c"><a class="header" href="#ping-s-c">PING (S, C)</a></h4>
+<p>Sent periodically to ensure the connection is still alive</p>
+<h4 id="name-c"><a class="header" href="#name-c">NAME (C)</a></h4>
+<p>Sent at the start by client to inform the server who they are</p>
+<h4 id="replicate-c"><a class="header" href="#replicate-c">REPLICATE (C)</a></h4>
+<p>Asks the server for the current position of all streams.</p>
+<h4 id="user_sync-c"><a class="header" href="#user_sync-c">USER_SYNC (C)</a></h4>
+<p>A user has started or stopped syncing on this process.</p>
+<h4 id="clear_user_sync-c"><a class="header" href="#clear_user_sync-c">CLEAR_USER_SYNC (C)</a></h4>
+<p>The server should clear all associated user sync data from the worker.</p>
+<p>This is used when a worker is shutting down.</p>
+<h4 id="federation_ack-c"><a class="header" href="#federation_ack-c">FEDERATION_ACK (C)</a></h4>
+<p>Acknowledge receipt of some federation data</p>
+<h3 id="remote_server_up-s-c"><a class="header" href="#remote_server_up-s-c">REMOTE_SERVER_UP (S, C)</a></h3>
+<p>Inform other processes that a remote server may have come back online.</p>
+<p>See <code>synapse/replication/tcp/commands.py</code> for a detailed description and
+the format of each command.</p>
+<h3 id="cache-invalidation-stream"><a class="header" href="#cache-invalidation-stream">Cache Invalidation Stream</a></h3>
+<p>The cache invalidation stream is used to inform workers when they need
+to invalidate any of their caches in the data store. This is done by
+streaming all cache invalidations done on master down to the workers,
+assuming that any caches on the workers also exist on the master.</p>
+<p>Each individual cache invalidation results in a row being sent down
+replication, which includes the cache name (the name of the function)
+and they key to invalidate. For example:</p>
+<pre><code>&gt; RDATA caches master 550953771 [&quot;get_user_by_id&quot;, [&quot;@bob:example.com&quot;], 1550574873251]
+</code></pre>
+<p>Alternatively, an entire cache can be invalidated by sending down a <code>null</code>
+instead of the key. For example:</p>
+<pre><code>&gt; RDATA caches master 550953772 [&quot;get_user_by_id&quot;, null, 1550574873252]
+</code></pre>
+<p>However, there are times when a number of caches need to be invalidated
+at the same time with the same key. To reduce traffic we batch those
+invalidations into a single poke by defining a special cache name that
+workers understand to mean to expand to invalidate the correct caches.</p>
+<p>Currently the special cache names are declared in
+<code>synapse/storage/_base.py</code> and are:</p>
+<ol>
+<li><code>cs_cache_fake</code> ─ invalidates caches that depend on the current
+state</li>
+</ol>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="internal-documentation"><a class="header" href="#internal-documentation">Internal Documentation</a></h1>
+<p>This section covers implementation documentation for various parts of Synapse.</p>
+<p>If a developer is planning to make a change to a feature of Synapse, it can be useful for
+general documentation of how that feature is implemented to be available. This saves the
+developer time in place of needing to understand how the feature works by reading the
+code.</p>
+<p>Documentation that would be more useful for the perspective of a system administrator,
+rather than a developer who's intending to change to code, should instead be placed
+under the Usage section of the documentation.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="how-to-test-saml-as-a-developer-without-a-server"><a class="header" href="#how-to-test-saml-as-a-developer-without-a-server">How to test SAML as a developer without a server</a></h1>
+<p>https://fujifish.github.io/samling/samling.html (https://github.com/fujifish/samling) is a great resource for being able to tinker with the 
+SAML options within Synapse without needing to deploy and configure a complicated software stack.</p>
+<p>To make Synapse (and therefore Element) use it:</p>
+<ol>
+<li>Use the samling.html URL above or deploy your own and visit the IdP Metadata tab.</li>
+<li>Copy the XML to your clipboard.</li>
+<li>On your Synapse server, create a new file <code>samling.xml</code> next to your <code>homeserver.yaml</code> with
+the XML from step 2 as the contents.</li>
+<li>Edit your <code>homeserver.yaml</code> to include:
+<pre><code class="language-yaml">saml2_config:
+  sp_config:
+    allow_unknown_attributes: true  # Works around a bug with AVA Hashes: https://github.com/IdentityPython/pysaml2/issues/388
+    metadata:
+      local: [&quot;samling.xml&quot;]
+</code></pre>
+</li>
+<li>Ensure that your <code>homeserver.yaml</code> has a setting for <code>public_baseurl</code>:
+<pre><code class="language-yaml">public_baseurl: http://localhost:8080/
+</code></pre>
+</li>
+<li>Run <code>apt-get install xmlsec1</code> and <code>pip install --upgrade --force 'pysaml2&gt;=4.5.0'</code> to ensure
+the dependencies are installed and ready to go.</li>
+<li>Restart Synapse.</li>
+</ol>
+<p>Then in Element:</p>
+<ol>
+<li>Visit the login page and point Element towards your homeserver using the <code>public_baseurl</code> above.</li>
+<li>Click the Single Sign-On button.</li>
+<li>On the samling page, enter a Name Identifier and add a SAML Attribute for <code>uid=your_localpart</code>.
+The response must also be signed.</li>
+<li>Click &quot;Next&quot;.</li>
+<li>Click &quot;Post Response&quot; (change nothing).</li>
+<li>You should be logged in.</li>
+</ol>
+<p>If you try and repeat this process, you may be automatically logged in using the information you
+gave previously. To fix this, open your developer console (<code>F12</code> or <code>Ctrl+Shift+I</code>) while on the
+samling page and clear the site data. In Chrome, this will be a button on the Application tab.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="how-to-test-cas-as-a-developer-without-a-server"><a class="header" href="#how-to-test-cas-as-a-developer-without-a-server">How to test CAS as a developer without a server</a></h1>
+<p>The <a href="https://github.com/jbittel/django-mama-cas">django-mama-cas</a> project is an
+easy to run CAS implementation built on top of Django.</p>
+<h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
+<ol>
+<li>Create a new virtualenv: <code>python3 -m venv &lt;your virtualenv&gt;</code></li>
+<li>Activate your virtualenv: <code>source /path/to/your/virtualenv/bin/activate</code></li>
+<li>Install Django and django-mama-cas:
+<pre><code class="language-sh">python -m pip install &quot;django&lt;3&quot; &quot;django-mama-cas==2.4.0&quot;
+</code></pre>
+</li>
+<li>Create a Django project in the current directory:
+<pre><code class="language-sh">django-admin startproject cas_test .
+</code></pre>
+</li>
+<li>Follow the <a href="https://django-mama-cas.readthedocs.io/en/latest/installation.html#configuring">install directions</a> for django-mama-cas</li>
+<li>Setup the SQLite database: <code>python manage.py migrate</code></li>
+<li>Create a user:
+<pre><code class="language-sh">python manage.py createsuperuser
+</code></pre>
+<ol>
+<li>Use whatever you want as the username and password.</li>
+<li>Leave the other fields blank.</li>
+</ol>
+</li>
+<li>Use the built-in Django test server to serve the CAS endpoints on port 8000:
+<pre><code class="language-sh">python manage.py runserver
+</code></pre>
+</li>
+</ol>
+<p>You should now have a Django project configured to serve CAS authentication with
+a single user created.</p>
+<h2 id="configure-synapse-and-element-to-use-cas"><a class="header" href="#configure-synapse-and-element-to-use-cas">Configure Synapse (and Element) to use CAS</a></h2>
+<ol>
+<li>Modify your <code>homeserver.yaml</code> to enable CAS and point it to your locally
+running Django test server:
+<pre><code class="language-yaml">cas_config:
+  enabled: true
+  server_url: &quot;http://localhost:8000&quot;
+  service_url: &quot;http://localhost:8081&quot;
+  #displayname_attribute: name
+  #required_attributes:
+  #    name: value
+</code></pre>
+</li>
+<li>Restart Synapse.</li>
+</ol>
+<p>Note that the above configuration assumes the homeserver is running on port 8081
+and that the CAS server is on port 8000, both on localhost.</p>
+<h2 id="testing-the-configuration"><a class="header" href="#testing-the-configuration">Testing the configuration</a></h2>
+<p>Then in Element:</p>
+<ol>
+<li>Visit the login page with a Element pointing at your homeserver.</li>
+<li>Click the Single Sign-On button.</li>
+<li>Login using the credentials created with <code>createsuperuser</code>.</li>
+<li>You should be logged in.</li>
+</ol>
+<p>If you want to repeat this process you'll need to manually logout first:</p>
+<ol>
+<li>http://localhost:8000/admin/</li>
+<li>Click &quot;logout&quot; in the top right.</li>
+</ol>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="room-dag-concepts"><a class="header" href="#room-dag-concepts">Room DAG concepts</a></h1>
+<h2 id="edges"><a class="header" href="#edges">Edges</a></h2>
+<p>The word &quot;edge&quot; comes from graph theory lingo. An edge is just a connection
+between two events. In Synapse, we connect events by specifying their
+<code>prev_events</code>. A subsequent event points back at a previous event.</p>
+<pre><code>A (oldest) &lt;---- B &lt;---- C (most recent)
+</code></pre>
+<h2 id="depth-and-stream-ordering"><a class="header" href="#depth-and-stream-ordering">Depth and stream ordering</a></h2>
+<p>Events are normally sorted by <code>(topological_ordering, stream_ordering)</code> where
+<code>topological_ordering</code> is just <code>depth</code>. In other words, we first sort by <code>depth</code>
+and then tie-break based on <code>stream_ordering</code>. <code>depth</code> is incremented as new
+messages are added to the DAG. Normally, <code>stream_ordering</code> is an auto
+incrementing integer, but backfilled events start with <code>stream_ordering=-1</code> and decrement.</p>
+<hr />
+<ul>
+<li><code>/sync</code> returns things in the order they arrive at the server (<code>stream_ordering</code>).</li>
+<li><code>/messages</code> (and <code>/backfill</code> in the federation API) return them in the order determined by the event graph <code>(topological_ordering, stream_ordering)</code>.</li>
+</ul>
+<p>The general idea is that, if you're following a room in real-time (i.e.
+<code>/sync</code>), you probably want to see the messages as they arrive at your server,
+rather than skipping any that arrived late; whereas if you're looking at a
+historical section of timeline (i.e. <code>/messages</code>), you want to see the best
+representation of the state of the room as others were seeing it at the time.</p>
+<h2 id="outliers"><a class="header" href="#outliers">Outliers</a></h2>
+<p>We mark an event as an <code>outlier</code> when we haven't figured out the state for the
+room at that point in the DAG yet. They are &quot;floating&quot; events that we haven't
+yet correlated to the DAG.</p>
+<p>Outliers typically arise when we fetch the auth chain or state for a given
+event. When that happens, we just grab the events in the state/auth chain,
+without calculating the state at those events, or backfilling their
+<code>prev_events</code>.</p>
+<p>So, typically, we won't have the <code>prev_events</code> of an <code>outlier</code> in the database,
+(though it's entirely possible that we <em>might</em> have them for some other
+reason). Other things that make outliers different from regular events:</p>
+<ul>
+<li>
+<p>We don't have state for them, so there should be no entry in
+<code>event_to_state_groups</code> for an outlier. (In practice this isn't always
+the case, though I'm not sure why: see https://github.com/matrix-org/synapse/issues/12201).</p>
+</li>
+<li>
+<p>We don't record entries for them in the <code>event_edges</code>,
+<code>event_forward_extremeties</code> or <code>event_backward_extremities</code> tables.</p>
+</li>
+</ul>
+<p>Since outliers are not tied into the DAG, they do not normally form part of the
+timeline sent down to clients via <code>/sync</code> or <code>/messages</code>; however there is an
+exception:</p>
+<h3 id="out-of-band-membership-events"><a class="header" href="#out-of-band-membership-events">Out-of-band membership events</a></h3>
+<p>A special case of outlier events are some membership events for federated rooms
+that we aren't full members of. For example:</p>
+<ul>
+<li>invites received over federation, before we join the room</li>
+<li><em>rejections</em> for said invites</li>
+<li>knock events for rooms that we would like to join but have not yet joined.</li>
+</ul>
+<p>In all the above cases, we don't have the state for the room, which is why they
+are treated as outliers. They are a bit special though, in that they are
+proactively sent to clients via <code>/sync</code>.</p>
+<h2 id="forward-extremity"><a class="header" href="#forward-extremity">Forward extremity</a></h2>
+<p>Most-recent-in-time events in the DAG which are not referenced by any other
+events' <code>prev_events</code> yet. (In this definition, outliers, rejected events, and
+soft-failed events don't count.)</p>
+<p>The forward extremities of a room (or at least, a subset of them, if there are
+more than ten) are used as the <code>prev_events</code> when the next event is sent.</p>
+<p>The &quot;current state&quot; of a room (ie: the state which would be used if we
+generated a new event) is, therefore, the resolution of the room states
+at each of the forward extremities.</p>
+<h2 id="backward-extremity"><a class="header" href="#backward-extremity">Backward extremity</a></h2>
+<p>The current marker of where we have backfilled up to and will generally be the
+<code>prev_events</code> of the oldest-in-time events we have in the DAG. This gives a starting point when
+backfilling history.</p>
+<p>Note that, unlike forward extremities, we typically don't have any backward
+extremity events themselves in the database - or, if we do, they will be &quot;outliers&quot; (see
+above). Either way, we don't expect to have the room state at a backward extremity.</p>
+<p>When we persist a non-outlier event, if it was previously a backward extremity,
+we clear it as a backward extremity and set all of its <code>prev_events</code> as the new
+backward extremities if they aren't already persisted as non-outliers. This
+therefore keeps the backward extremities up-to-date.</p>
+<h2 id="state-groups"><a class="header" href="#state-groups">State groups</a></h2>
+<p>For every non-outlier event we need to know the state at that event. Instead of
+storing the full state for each event in the DB (i.e. a <code>event_id -&gt; state</code>
+mapping), which is <em>very</em> space inefficient when state doesn't change, we
+instead assign each different set of state a &quot;state group&quot; and then have
+mappings of <code>event_id -&gt; state_group</code> and <code>state_group -&gt; state</code>.</p>
+<h3 id="stage-group-edges"><a class="header" href="#stage-group-edges">Stage group edges</a></h3>
+<p>TODO: <code>state_group_edges</code> is a further optimization...
+notes from @Azrenbeth, https://pastebin.com/seUGVGeT</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="auth-chain-difference-algorithm"><a class="header" href="#auth-chain-difference-algorithm">Auth Chain Difference Algorithm</a></h1>
+<p>The auth chain difference algorithm is used by V2 state resolution, where a
+naive implementation can be a significant source of CPU and DB usage.</p>
+<h3 id="definitions"><a class="header" href="#definitions">Definitions</a></h3>
+<p>A <em>state set</em> is a set of state events; e.g. the input of a state resolution
+algorithm is a collection of state sets.</p>
+<p>The <em>auth chain</em> of a set of events are all the events' auth events and <em>their</em>
+auth events, recursively (i.e. the events reachable by walking the graph induced
+by an event's auth events links).</p>
+<p>The <em>auth chain difference</em> of a collection of state sets is the union minus the
+intersection of the sets of auth chains corresponding to the state sets, i.e an
+event is in the auth chain difference if it is reachable by walking the auth
+event graph from at least one of the state sets but not from <em>all</em> of the state
+sets.</p>
+<h2 id="breadth-first-walk-algorithm"><a class="header" href="#breadth-first-walk-algorithm">Breadth First Walk Algorithm</a></h2>
+<p>A way of calculating the auth chain difference without calculating the full auth
+chains for each state set is to do a parallel breadth first walk (ordered by
+depth) of each state set's auth chain. By tracking which events are reachable
+from each state set we can finish early if every pending event is reachable from
+every state set.</p>
+<p>This can work well for state sets that have a small auth chain difference, but
+can be very inefficient for larger differences. However, this algorithm is still
+used if we don't have a chain cover index for the room (e.g. because we're in
+the process of indexing it).</p>
+<h2 id="chain-cover-index"><a class="header" href="#chain-cover-index">Chain Cover Index</a></h2>
+<p>Synapse computes auth chain differences by pre-computing a &quot;chain cover&quot; index
+for the auth chain in a room, allowing efficient reachability queries like &quot;is
+event A in the auth chain of event B&quot;. This is done by assigning every event a
+<em>chain ID</em> and <em>sequence number</em> (e.g. <code>(5,3)</code>), and having a map of <em>links</em>
+between chains (e.g. <code>(5,3) -&gt; (2,4)</code>) such that A is reachable by B (i.e. <code>A</code>
+is in the auth chain of <code>B</code>) if and only if either:</p>
+<ol>
+<li>A and B have the same chain ID and <code>A</code>'s sequence number is less than <code>B</code>'s
+sequence number; or</li>
+<li>there is a link <code>L</code> between <code>B</code>'s chain ID and <code>A</code>'s chain ID such that
+<code>L.start_seq_no</code> &lt;= <code>B.seq_no</code> and <code>A.seq_no</code> &lt;= <code>L.end_seq_no</code>.</li>
+</ol>
+<p>There are actually two potential implementations, one where we store links from
+each chain to every other reachable chain (the transitive closure of the links
+graph), and one where we remove redundant links (the transitive reduction of the
+links graph) e.g. if we have chains <code>C3 -&gt; C2 -&gt; C1</code> then the link <code>C3 -&gt; C1</code>
+would not be stored. Synapse uses the former implementations so that it doesn't
+need to recurse to test reachability between chains.</p>
+<h3 id="example-5"><a class="header" href="#example-5">Example</a></h3>
+<p>An example auth graph would look like the following, where chains have been
+formed based on type/state_key and are denoted by colour and are labelled with
+<code>(chain ID, sequence number)</code>. Links are denoted by the arrows (links in grey
+are those that would be remove in the second implementation described above).</p>
+<p><img src="auth_chain_diff.dot.png" alt="Example" /></p>
+<p>Note that we don't include all links between events and their auth events, as
+most of those links would be redundant. For example, all events point to the
+create event, but each chain only needs the one link from it's base to the
+create event.</p>
+<h2 id="using-the-index"><a class="header" href="#using-the-index">Using the Index</a></h2>
+<p>This index can be used to calculate the auth chain difference of the state sets
+by looking at the chain ID and sequence numbers reachable from each state set:</p>
+<ol>
+<li>For every state set lookup the chain ID/sequence numbers of each state event</li>
+<li>Use the index to find all chains and the maximum sequence number reachable
+from each state set.</li>
+<li>The auth chain difference is then all events in each chain that have sequence
+numbers between the maximum sequence number reachable from <em>any</em> state set and
+the minimum reachable by <em>all</em> state sets (if any).</li>
+</ol>
+<p>Note that steps 2 is effectively calculating the auth chain for each state set
+(in terms of chain IDs and sequence numbers), and step 3 is calculating the
+difference between the union and intersection of the auth chains.</p>
+<h3 id="worked-example"><a class="header" href="#worked-example">Worked Example</a></h3>
+<p>For example, given the above graph, we can calculate the difference between
+state sets consisting of:</p>
+<ol>
+<li><code>S1</code>: Alice's invite <code>(4,1)</code> and Bob's second join <code>(2,2)</code>; and</li>
+<li><code>S2</code>: Alice's second join <code>(4,3)</code> and Bob's first join <code>(2,1)</code>.</li>
+</ol>
+<p>Using the index we see that the following auth chains are reachable from each
+state set:</p>
+<ol>
+<li><code>S1</code>: <code>(1,1)</code>, <code>(2,2)</code>, <code>(3,1)</code> &amp; <code>(4,1)</code></li>
+<li><code>S2</code>: <code>(1,1)</code>, <code>(2,1)</code>, <code>(3,2)</code> &amp; <code>(4,3)</code></li>
+</ol>
+<p>And so, for each the ranges that are in the auth chain difference:</p>
+<ol>
+<li>Chain 1: None, (since everything can reach the create event).</li>
+<li>Chain 2: The range <code>(1, 2]</code> (i.e. just <code>2</code>), as <code>1</code> is reachable by all state
+sets and the maximum reachable is <code>2</code> (corresponding to Bob's second join).</li>
+<li>Chain 3: Similarly the range <code>(1, 2]</code> (corresponding to the second power
+level).</li>
+<li>Chain 4: The range <code>(1, 3]</code> (corresponding to both of Alice's joins).</li>
+</ol>
+<p>So the final result is: Bob's second join <code>(2,2)</code>, the second power level
+<code>(3,2)</code> and both of Alice's joins <code>(4,2)</code> &amp; <code>(4,3)</code>.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="media-repository"><a class="header" href="#media-repository">Media Repository</a></h1>
+<p><em>Synapse implementation-specific details for the media repository</em></p>
+<p>The media repository</p>
+<ul>
+<li>stores avatars, attachments and their thumbnails for media uploaded by local
+users.</li>
+<li>caches avatars, attachments and their thumbnails for media uploaded by remote
+users.</li>
+<li>caches resources and thumbnails used for
+<a href="development/url_previews.html">URL previews</a>.</li>
+</ul>
+<p>All media in Matrix can be identified by a unique
+<a href="https://spec.matrix.org/latest/client-server-api/#matrix-content-mxc-uris">MXC URI</a>,
+consisting of a server name and media ID:</p>
+<pre><code>mxc://&lt;server-name&gt;/&lt;media-id&gt;
+</code></pre>
+<h2 id="local-media"><a class="header" href="#local-media">Local Media</a></h2>
+<p>Synapse generates 24 character media IDs for content uploaded by local users.
+These media IDs consist of upper and lowercase letters and are case-sensitive.
+Other homeserver implementations may generate media IDs differently.</p>
+<p>Local media is recorded in the <code>local_media_repository</code> table, which includes
+metadata such as MIME types, upload times and file sizes.
+Note that this table is shared by the URL cache, which has a different media ID
+scheme.</p>
+<h3 id="paths"><a class="header" href="#paths">Paths</a></h3>
+<p>A file with media ID <code>aabbcccccccccccccccccccc</code> and its <code>128x96</code> <code>image/jpeg</code>
+thumbnail, created by scaling, would be stored at:</p>
+<pre><code>local_content/aa/bb/cccccccccccccccccccc
+local_thumbnails/aa/bb/cccccccccccccccccccc/128-96-image-jpeg-scale
+</code></pre>
+<h2 id="remote-media"><a class="header" href="#remote-media">Remote Media</a></h2>
+<p>When media from a remote homeserver is requested from Synapse, it is assigned
+a local <code>filesystem_id</code>, with the same format as locally-generated media IDs,
+as described above.</p>
+<p>A record of remote media is stored in the <code>remote_media_cache</code> table, which
+can be used to map remote MXC URIs (server names and media IDs) to local
+<code>filesystem_id</code>s.</p>
+<h3 id="paths-1"><a class="header" href="#paths-1">Paths</a></h3>
+<p>A file from <code>matrix.org</code> with <code>filesystem_id</code> <code>aabbcccccccccccccccccccc</code> and its
+<code>128x96</code> <code>image/jpeg</code> thumbnail, created by scaling, would be stored at:</p>
+<pre><code>remote_content/matrix.org/aa/bb/cccccccccccccccccccc
+remote_thumbnail/matrix.org/aa/bb/cccccccccccccccccccc/128-96-image-jpeg-scale
+</code></pre>
+<p>Older thumbnails may omit the thumbnailing method:</p>
+<pre><code>remote_thumbnail/matrix.org/aa/bb/cccccccccccccccccccc/128-96-image-jpeg
+</code></pre>
+<p>Note that <code>remote_thumbnail/</code> does not have an <code>s</code>.</p>
+<h2 id="url-previews-2"><a class="header" href="#url-previews-2">URL Previews</a></h2>
+<p>See <a href="development/url_previews.html">URL Previews</a> for documentation on the URL preview
+process.</p>
+<p>When generating previews for URLs, Synapse may download and cache various
+resources, including images. These resources are assigned temporary media IDs
+of the form <code>yyyy-mm-dd_aaaaaaaaaaaaaaaa</code>, where <code>yyyy-mm-dd</code> is the current
+date and <code>aaaaaaaaaaaaaaaa</code> is a random sequence of 16 case-sensitive letters.</p>
+<p>The metadata for these cached resources is stored in the
+<code>local_media_repository</code> and <code>local_media_repository_url_cache</code> tables.</p>
+<p>Resources for URL previews are deleted after a few days.</p>
+<h3 id="paths-2"><a class="header" href="#paths-2">Paths</a></h3>
+<p>The file with media ID <code>yyyy-mm-dd_aaaaaaaaaaaaaaaa</code> and its <code>128x96</code>
+<code>image/jpeg</code> thumbnail, created by scaling, would be stored at:</p>
+<pre><code>url_cache/yyyy-mm-dd/aaaaaaaaaaaaaaaa
+url_cache_thumbnails/yyyy-mm-dd/aaaaaaaaaaaaaaaa/128-96-image-jpeg-scale
+</code></pre>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="room-and-user-statistics"><a class="header" href="#room-and-user-statistics">Room and User Statistics</a></h1>
+<p>Synapse maintains room and user statistics in various tables. These can be used
+for administrative purposes but are also used when generating the public room
+directory.</p>
+<h1 id="synapse-developer-documentation"><a class="header" href="#synapse-developer-documentation">Synapse Developer Documentation</a></h1>
+<h2 id="high-level-concepts"><a class="header" href="#high-level-concepts">High-Level Concepts</a></h2>
+<h3 id="definitions-1"><a class="header" href="#definitions-1">Definitions</a></h3>
+<ul>
+<li><strong>subject</strong>: Something we are tracking stats about – currently a room or user.</li>
+<li><strong>current row</strong>: An entry for a subject in the appropriate current statistics
+table. Each subject can have only one.</li>
+</ul>
+<h3 id="overview-4"><a class="header" href="#overview-4">Overview</a></h3>
+<p>Stats correspond to the present values. Current rows contain the most up-to-date
+statistics for a room. Each subject can only have one entry.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="deprecation-policy-for-platform-dependencies"><a class="header" href="#deprecation-policy-for-platform-dependencies">Deprecation Policy for Platform Dependencies</a></h1>
+<p>Synapse has a number of platform dependencies, including Python and PostgreSQL.
+This document outlines the policy towards which versions we support, and when we
+drop support for versions in the future.</p>
+<h2 id="policy"><a class="header" href="#policy">Policy</a></h2>
+<p>Synapse follows the upstream support life cycles for Python and PostgreSQL,
+i.e. when a version reaches End of Life Synapse will withdraw support for that
+version in future releases.</p>
+<p>Details on the upstream support life cycles for Python and PostgreSQL are
+documented at <a href="https://endoflife.date/python">https://endoflife.date/python</a> and
+<a href="https://endoflife.date/postgresql">https://endoflife.date/postgresql</a>.</p>
+<h2 id="context"><a class="header" href="#context">Context</a></h2>
+<p>It is important for system admins to have a clear understanding of the platform
+requirements of Synapse and its deprecation policies so that they can
+effectively plan upgrading their infrastructure ahead of time. This is
+especially important in contexts where upgrading the infrastructure requires
+auditing and approval from a security team, or where otherwise upgrading is a
+long process.</p>
+<p>By following the upstream support life cycles Synapse can ensure that its
+dependencies continue to get security patches, while not requiring system admins
+to constantly update their platform dependencies to the latest versions.</p>
+<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 id="summary-of-performance-impact-of-running-on-resource-constrained-devices-such-as-sbcs"><a class="header" href="#summary-of-performance-impact-of-running-on-resource-constrained-devices-such-as-sbcs">Summary of performance impact of running on resource constrained devices such as SBCs</a></h2>
+<p>I've been running my homeserver on a cubietruck at home now for some time and am often replying to statements like &quot;you need loads of ram to join large rooms&quot; with &quot;it works fine for me&quot;. I thought it might be useful to curate a summary of the issues you're likely to run into to help as a scaling-down guide, maybe highlight these for development work or end up as documentation. It seems that once you get up to about 4x1.5GHz arm64 4GiB these issues are no longer a problem.</p>
+<ul>
+<li><strong>Platform</strong>: 2x1GHz armhf 2GiB ram <a href="https://wiki.debian.org/CheapServerBoxHardware">Single-board computers</a>, SSD, postgres.</li>
+</ul>
+<h3 id="presence"><a class="header" href="#presence">Presence</a></h3>
+<p>This is the main reason people have a poor matrix experience on resource constrained homeservers. Element web will frequently be saying the server is offline while the python process will be pegged at 100% cpu. This feature is used to tell when other users are active (have a client app in the foreground) and therefore more likely to respond, but requires a lot of network activity to maintain even when nobody is talking in a room.</p>
+<p><img src="https://user-images.githubusercontent.com/71895/94848963-a47a3580-041c-11eb-8b6e-acb772b4259e.png" alt="Screenshot_2020-10-01_19-29-46" /></p>
+<p>While synapse does have some performance issues with presence <a href="https://github.com/matrix-org/synapse/issues/3971">#3971</a>, the fundamental problem is that this is an easy feature to implement for a centralised service at nearly no overhead, but federation makes it combinatorial <a href="https://github.com/matrix-org/synapse/issues/8055">#8055</a>. There is also a client-side config option which disables the UI and idle tracking <a href="https://github.com/vector-im/element-web/blob/v1.7.8/config.sample.json#L45">enable_presence_by_hs_url</a> to blacklist the largest instances but I didn't notice much difference, so I recommend disabling the feature entirely at the server level as well.</p>
+<h3 id="joining"><a class="header" href="#joining">Joining</a></h3>
+<p>Joining a &quot;large&quot;, federated room will initially fail with the below message in Element web, but waiting a while (10-60mins) and trying again will succeed without any issue. What counts as &quot;large&quot; is not message history, user count, connections to homeservers or even a simple count of the state events, it is instead how long the state resolution algorithm takes. However, each of those numbers are reasonable proxies, so we can use them as estimates since user count is one of the few things you see before joining.</p>
+<p><img src="https://user-images.githubusercontent.com/71895/94945781-18771500-04d3-11eb-8419-83c2da73a341.png" alt="Screenshot_2020-10-02_17-15-06" /></p>
+<p>This is <a href="https://github.com/matrix-org/synapse/issues/1211">#1211</a> and will also hopefully be mitigated by peeking <a href="https://github.com/matrix-org/matrix-doc/pull/2753">matrix-org/matrix-doc#2753</a> so at least you don't need to wait for a join to complete before finding out if it's the kind of room you want. Note that you should first disable presence, otherwise it'll just make the situation worse <a href="https://github.com/matrix-org/synapse/issues/3120">#3120</a>. There is a lot of database interaction too, so make sure you've <a href="other/../postgres.html">migrated your data</a> from the default sqlite to postgresql. Personally, I recommend patience - once the initial join is complete there's rarely any issues with actually interacting with the room, but if you like you can just block &quot;large&quot; rooms entirely.</p>
+<h3 id="sessions"><a class="header" href="#sessions">Sessions</a></h3>
+<p>Anything that requires modifying the device list <a href="https://github.com/matrix-org/synapse/issues/7721">#7721</a> will take a while to propagate, again taking the client &quot;Offline&quot; until it's complete. This includes signing in and out, editing the public name and verifying e2ee. The main mitigation I recommend is to keep long-running sessions open e.g. by using Firefox SSB &quot;Use this site in App mode&quot; or Chromium PWA &quot;Install Element&quot;.</p>
+<h3 id="recommended-configuration"><a class="header" href="#recommended-configuration">Recommended configuration</a></h3>
+<p>Put the below in a new file at /etc/matrix-synapse/conf.d/sbc.yaml to override the defaults in homeserver.yaml.</p>
+<pre><code># Disable presence tracking, which is currently fairly resource intensive
+# More info: https://github.com/matrix-org/synapse/issues/9478
+use_presence: false
+
+# Set a small complexity limit, preventing users from joining large rooms
+# which may be resource-intensive to remain a part of.
+#
+# Note that this will not prevent users from joining smaller rooms that
+# eventually become complex.
+limit_remote_rooms:
+  enabled: true
+  complexity: 3.0
+
+# Database configuration
+database:
+  # Use postgres for the best performance
+  name: psycopg2
+  args:
+    user: matrix-synapse
+    # Generate a long, secure password using a password manager
+    password: hunter2
+    database: matrix-synapse
+    host: localhost
+</code></pre>
+<p>Currently the complexity is measured by <a href="https://github.com/matrix-org/synapse/blob/v1.20.1/synapse/storage/databases/main/events_worker.py#L986">current_state_events / 500</a>. You can find join times and your most complex rooms like this:</p>
+<pre><code>admin@homeserver:~$ zgrep '/client/r0/join/' /var/log/matrix-synapse/homeserver.log* | awk '{print $18, $25}' | sort --human-numeric-sort
+29.922sec/-0.002sec /_matrix/client/r0/join/%23debian-fasttrack%3Apoddery.com
+182.088sec/0.003sec /_matrix/client/r0/join/%23decentralizedweb-general%3Amatrix.org
+911.625sec/-570.847sec /_matrix/client/r0/join/%23synapse%3Amatrix.org
+
+admin@homeserver:~$ sudo --user postgres psql matrix-synapse --command 'select canonical_alias, joined_members, current_state_events from room_stats_state natural join room_stats_current where canonical_alias is not null order by current_state_events desc fetch first 5 rows only'
+        canonical_alias        | joined_members | current_state_events 
+-------------------------------+----------------+----------------------
+ #_oftc_#debian:matrix.org             |  871   |  52355
+ #matrix:matrix.org                    |  6379  |  10684
+ #irc:matrix.org                       |  461   |  3751
+ #decentralizedweb-general:matrix.org  |  997   |  1509
+ #whatsapp:maunium.net                 |  554   |  854
+</code></pre>
+
+                    </main>
+
+                    <nav class="nav-wrapper" aria-label="Page navigation">
+                        <!-- Mobile navigation buttons -->
+                        
+
+                        
+
+                        <div style="clear: both"></div>
+                    </nav>
+                </div>
+            </div>
+
+            <nav class="nav-wide-wrapper" aria-label="Page navigation">
+                
+
+                
+            </nav>
+
+        </div>
+
+        
+
+        
+
+        
+
+        
+        <script type="text/javascript">
+            window.playground_copyable = true;
+        </script>
+        
+
+        
+
+        
+        <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
+        <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
+        <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
+        
+
+        <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
+        <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
+        <script src="book.js" type="text/javascript" charset="utf-8"></script>
+
+        <!-- Custom JS scripts -->
+        
+        <script type="text/javascript" src="docs/website_files/table-of-contents.js"></script>
+        
+
+        
+        
+        <script type="text/javascript">
+        window.addEventListener('load', function() {
+            window.setTimeout(window.print, 100);
+        });
+        </script>
+        
+        
+
+    </body>
+</html>
\ No newline at end of file