Category – Tutorials
9 posts tagged with "Tutorials" (See all categories)

Low Bandwidth Matrix: An implementation guide

2021-06-10 — Tutorials — Kegan Dougal

Disclaimer: Low bandwidth Matrix is experimental, not yet standardised, and subject to change without notice.

This guide is for Matrix developers who want to support MSC3079: Low Bandwidth CS API in their clients/servers. Please read the experimental MSC if you want to learn more about what is happening at a protocol level. If you want a high level overview of low bandwidth Matrix and why you should care, watch the 12 minute demo on Matrix Live.

Matrix currently uses HTTP APIs with JSON data to communicate from the client to the server. This is widely supported but is not very bandwidth efficient. This means that the protocol is slower, more costly and less able to be used on low bandwidth links (e.g 2G networks) which are common in certain parts of the world. MSC3079 defines a low bandwidth protocol using CoAP and CBOR instead of HTTP and JSON respectively. In the future homeservers will natively support some form of low bandwidth protocol. However, at present, no homeserver natively supports MSC3079. Therefore, this guide will set up a low bandwidth proxy server which can be put in front of any Matrix homeserver (Synapse, Dendrite, Conduit, etc) to make it MSC3079-compatible. This guide will also configure an Android device to speak MSC3079.

Low bandwidth Matrix currently does not support web browsers due to their inability to send UDP traffic. You do not need to be running a homeserver to follow this tutorial.

Setting up a low bandwidth proxy for your homeserver


  • Go 1.13+
  • openssl to generate a self-signed DTLS certificate, or an existing certificate you want to use.
  • Linux or Mac user


  • Clone the repo: git clone
  • Build the low bandwidth proxy: go build ./cmd/proxy
  • Generate a elliptic curve DTLS key/certificate: (we use curve keys as they are smaller than RSA keys, but both work.)
    openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
    openssl req -new -x509 -key private-key.pem -out cert.pem -days 365
    # you now have cert.pem and private-key.pem
  • Run it pointing at
    ./proxy -local '' \
    --tls-cert cert.pem --tls-key private-key.pem \
    --advertise "" \
    --dtls-bind-addr :8008
  • You should see something like this:
    INFO[0000] Listening on :8008/tcp to reverse proxy from to - HTTPS enabled: false 
    INFO[0000] Listening for DTLS on :8008 - ACK piggyback period: 5s

Mac users: If you are having trouble generating EC certificates, make sure you are using OpenSSL and not LibreSSL which comes by default: openssl version. To use OpenSSL, brew install openssl which then dumps the binary to /usr/local/opt/openssl/bin/openssl.

To test it is working correctly:

# build command line tools we can use to act as a low bandwidth client
go build ./cmd/jc
go build ./cmd/coap

# do a CoAP GET request to via the proxy
./coap -X GET -k 'http://localhost:8008/_matrix/client/versions' | ./jc -c2j '-'


If this doesn't work:

  • Check the proxy logs for errors (e.g bad hostname)
  • Try adding -v to ./coap (e.g bad method or path)
  • Run the proxy with SSLKEYLOGFILE=ssl.log and inspect the decrypted traffic using Wireshark.

Otherwise, congratulations! You now have a low bandwidth proxy! You can connect to your proxy just like you would to or any other homeserver.

Security considerations

  • The proxy acts as a man in the middle and can read all non-E2EE traffic, including login credentials. DO NOT USE UNTRUSTED LOW BANDWIDTH PROXY SERVERS. Only use proxy servers run by yourself or the homeserver admins.

Further reading

Setting up a custom Element Android

We'll add low bandwidth matrix to Element Android and iOS by default once it's standardised - but while things are still experimental, here's a guide for how to build Element Android to do it yourself if you feel the urge. This can be used as inspiration for other Matrix clients too.


  • Android Studio


  • Clone the repo: git clone
  • Checkout kegan/lb: git checkout kegan/lb. This branch replaces all HTTP traffic going to /_matrix/client/* with LB traffic. /_matrix/media traffic is left untouched. This branch also disables TLS checks entirely so self-signed certificates will work.
  • Clone the low bandwidth repo if you haven't already: git clone
  • In the low bandwidth repo, build the mobile bindings:
    go get
    cd mobile
    # if gomobile isn't on your path, then ~/go/bin/gomobile
    gomobile bind -target=android
  • Copy the output files to a directory in the Element Android repo which Gradle will pick up:
    mkdir $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile-sources.jar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile.aar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
  • Open the project in Android Studio.
  • Build and run on a device/emulator.
  • Configure the proxy's --advertise address. If you are running on a local device, restart the proxy with an --advertise of your machines LAN IP e.g instead of If you are running on an emulator, restart the proxy with an --advertise of the host IP: The URL scheme should be https not http, else image loading won't work as Element Android won't download media over http.
  • Login to your account via the proxy with the --advertise address as the HS URL e.g or The port is important.

To verify it is running via low bandwidth:

  • Install Wireshark.
  • Restart the proxy with the environment variable SSLKEYLOGFILE=ssl.log.
  • Run tcpdump on the right interface e.g: sudo tcpdump -i en0 -s 0 -v port 8008 -w lb.pcap
  • Force stop the android app to forcibly close any existing DTLS connections.
  • Re-open the app.
  • Open lb.pcap in Wireshark and set ssl.log as the Pre-Master Secret log filename via Preferences -> Protocols -> TLS -> Pre-Master Secret log filename.
  • Check there is DTLS/CoAP traffic.


To send a single 'Hello World' message to /room/$room_id/send/$txn_id and receive the response, including connection setup:

ProtocolNum packetsTotal bytes


  • CoAP OBSERVE is not enabled by default. This extension allows the server to push data to the client so the client doesn't need to long-poll. It is not yet enabled because of the risk of state synchronisation issues between the proxy and the client. If the proxy gets restarted, the client will not receive sync updates until it refreshes its subscription, which happens infrequently. During this time the client is not aware that anything is wrong.
  • CoAP uses Blockwise Transfer to download large responses. Each block must be ACKed before the next block can be sent. This is less efficient than TCP which has a Receive Window which allows multiple in-flight packets at once. This means CoAP is worse at downloading large responses, requiring more round trips to completely send the data.
  • The current version of /sync sends back much more data than is strictly necessary. This means the initial sync can be slower than expected. On a low kbps link this can flood the network with so much data that the sync stream begins to fall behind. Future work will look to optimise the sync API.
  • The proxy currently doesn't implement the low bandwidth response in /versions.

Usage of matrix-nio (Python Sans IO)

2019-07-03 — Tutorials — Ben Parsons

Canonical version of this article at

This article concerns matrix-nio, and asyncio. We'll build a simple "echo bot", meaning a bot which replies to messages with the text it has just read. Note that this article does not cover E2EE with matrix-nio.

Instantiation and Login

First create a new venv, and install matrix-nio via pip. On the command line, run:

python3 -m venv env
source env/bin/activate
pip install matrix-nio

Next, create a new Python file, and open it for editing. We'll import everything we require for this tutorial:

from importlib import util
import asyncio
from nio import (AsyncClient, SyncResponse, RoomMessageText)

We're importing asyncio so we can use the AsyncClient class from matrix-nio.

Create a new instance of AsyncClient by passing the homeserver and username as arguments:

async_client = AsyncClient(
    "", "%%YOUR-USERNAME-HERE%%"

Then login, and await the response:

response = await async_client.login("%%YOUR-PASSWORD-HERE%%")

Of course, we are using an async client, and awaiting the response. Because of this, we must call the async_client.login() from an async method, like so:

async def main():
    response = await async_client.login("%%YOUR-PASSWORD-HERE%%")

Note that for versions of Python before 3.7 the asyncio syntax must be:

async def main():
    response = await async_client.login("%%YOUR-PASSWORD-HERE%%")

loop = asyncio.get_event_loop()

The remainder of this tutorial assumes you are running everything from an async method.

The response string should look like:

Logged in as, device id: ZBLAJHLKVP.

Get into a /sync loop

To get updates from a Matrix homeserver to the client, the client makes a request to the /sync endpoint. In the matrix-nio AsyncClient, this is wrapped by the sync() method. We can get the latest updates:

sync_response = await async_client.sync(30000)

30000 means we will wait up to 30 seconds before returning. sync_response will now contain a Python object containing a mapping of the (JSON) response from the Matrix homeserver. We'll inspect this response in the next section.

In fact, we expect there to be updates regularly, so let's create a very simple loop:

while (True):
    sync_response = await async_client.sync(30000)
    print(sync_response) # note that this could be LARGE!
    # do some reading from sync_response

In this way, every time there is a response (i.e. new events) from the homeserver, they are made available in sync_response for processing, and we loop again.

Explore the sync response object

sync_response can contain multitudes, depending on the rooms this user is part of, or has been part of. sync_response.rooms.join contains updates for the rooms which the current user is "joined to" (meaning, is a member of.)

Of these joined rooms, we are (perhaps!) most interested in the events on the timeline. These are stored in, see below:

if len(sync_response.rooms.join) > 0:

    joins = sync_response.rooms.join
    for room_id in joins:
        for event in joins[room_id]

Message events are a specific type of event which contain an Instant Messenger message. We can check the type before proceeding:

for event in joins[room_id]
    if isinstance(event, RoomMessageText):
        print (event.body)

In these cases, where the event is a message to a room, the body field will contain the message text.

Isolate specific message event objects

Knowing that we can get the message text from an event, we can read it to determine a response. Let's make a new variable and have it store some string we'll check for:

response_string = "!replybot"

Now let's suppose we're in our /sync loop, and just received an event. We can filter messages that are meant for our bot as follows:

if len(sync_response.rooms.join) > 0:
    joins = sync_response.rooms.join
    for room_id in joins:
        for event in joins[room_id]
            if hasattr(event, 'body') and event.body.startswith(response_string):

Use room_send

To send messages, matrix-nio provides a room_send() method. There are three arguments:

  • the room_id
  • the message type, we will use ""
  • a JSON object representing the content of the message

Let's improve the example above, by sending back a message to echo the ones we isolated above:

joins = sync_response.rooms.join
for room_id in joins:
    for event in joins[room_id]
        if hasattr(event, 'body') and event.body.startswith(response_string):
            response_body = event.body.replace(response_string, "").strip()
            content = {
               "body": response_body,
               "msgtype": "m.text"
            await async_client.room_send(room_id, '', content)

Now whenever the bot receives a message "!replybot some message" it will send back "some message".

Use of /sync next_batch tokens

Finally, let's consider the importance of next_batch tokens. Whenever you receive a response from the /sync endpoint, the response will contain a "next_batch" field, which you then pass on the next request to ensure you have the latest messages. matrix-nio keeps track of this automatically, so it doesn't get repeated messages. However, when you stop the program and call the .sync() method again, how can you tell it where to start from? First let's get the latest next_batch token:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    while (True):
        sync_response = await async_client.sync(30000)
        print(sync_response.next_batch) # this is the token

Then we'll write the token to a file:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    while (True):
        sync_response = await async_client.sync(30000)

        # we write the token to a file here
        with open("next_batch","w") as next_batch_token:

Once that token is written, we know we can re-use it for the first /sync/ request next time:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    # we read the previously-written token...
    with open ("next_batch","r") as next_batch_token:
        # ... and well async_client to use it
        async_client.next_batch =

    while (True):
        sync_response = await async_client.sync(30000)
        with open("next_batch","w") as next_batch_token:


With this, you can see that in very few lines, it's possible to write a working Matrix bot in Python, using matrix-nio.

Bridging Matrix with WhatsApp running on a VM

2019-02-26 — Tutorials — Ben Parsons

This guide will live with the documentation at, but you can find the text below.

Matrix is:

an open standard for interoperable, decentralised, real-time communication.
In this article we'll benefit from all three of these attributes:
  • interoperable: we'll see how Matrix can be made to interact with WhatsApp
  • decentralised: you can perform this on your own server while still enjoying the benefits of being connected to the rest of the Matrix federation
  • real-time communication: we'll see how to send and receive messages in real-time

Install your homeserver and install mautrix-whatsapp, the WhatsApp bridge

Firstly, you need to have a Matrix homeserver installed. If you don't currently have one, take a look at the instructions at Installing Synapse, and also in the Synapse README.

Next, install mautrix-whatsapp by following the instructions at mautrix-whatsapp/wiki.

If you are starting from scratch, I suggest you take a look at matrix-docker-ansible-deploy, as this project will enable you to deploy Synapse, mautrix-whatsapp and other components easily.

For example, if you have an existing deployment using matrix-docker-ansible-deploy, you can add mautrix-whatsapp simply by adding the following line to your configuration file:

matrix_mautrix_whatsapp_enabled: true

... and re-running the setup:

ansible-playbook -i inventory/hosts setup.yml --tags=setup-all

Either way, you will soon have a functioning Matrix Synapse homeserver and mautrix-whatsapp installed with it. Next, we will set up an Android VM.

Set up an Android VM

The best way to run an Android Virtual Machine is to use the Android Studio tools from Google. First, install Android Studio, making sure to follow the post-install steps, as they will install additional tools we need, including AVD Manager.

Once installed, run AVD manager by choosing Tools -> AVD Manager from the menu.

Follow the steps to create a new virtual machine, in this example I have a Nexus 5X running Android 9, but almost any configuration is fine here. Make sure that you give the device access to the Play Store.

Install WhatsApp and sign-in

Launch the Virtual Device, the open the Play Store and sign in. Now use the Play Store to install WhatsApp on the Virtual Device.

You will be asked to verify your phone number, use your number on another device to complete this step.

Setup mautrix-whatsapp bridge

Now that you have WhatsApp working in a VM, and Matrix working on your server, it's time to bridge them togther!

Per the instructions at mautrix-whatsapp/wiki, you must start a new chat with @whatsappbot:<yourdomain>. Type login to begin the authentication process.

mautrix-whatsapp operates by using the WhatsApp Web feature of WhatsApp - which means it uses a QR code that you must now scan on the device running WhatsApp - which in your case is the AVD. In order to scan the presented QR code, set your AVD camera to passthrough the camera device on your host machine - see the images below.

Once this is complete, you can type sync, to start bridging contacts, and sync --create to automatically create room invites.

And that's it! You may need to take a little time to watch the sync happen, particularly if you have a very large number of chats on the WhatsApp side, but there is no further configuration needed.



Usage of the matrix-js-sdk

2018-10-16 — Tutorials — Ben Parsons

We have a brand new, exciting guide page offering an introduction to matrix-js-sdk. This guide will live with the documentation at,  but you can find the text below.

Matrix allows open real-time communications over the Internet using HTTP and JSON. This makes developing clients to connect to Matrix servers really easy! Because it's open, and uses simple syntax for messages, you can connect Matrix to anything that communicates over a standard HTTP interface - later projects in this series will explore ideas such as building bots, performing machine learning on message content, and connecting IoT devices such as Philips Hue lights.

Making a Matrix Client

Let's explore how we would make a very simple Matrix client, with only the ability to perform an initial sync, and to get member lists and the timeline for rooms of our choice.

This article will explore the Matrix Client-Server API, making use of the matrix-js-sdk. Later articles may discuss making the underlying calls. Specifically we will cover:

  • login
  • simple syncing
  • listening for timeline events
  • access the MatrixInMemoryStore
  • sending messages to rooms
  • how to respond to specific messages, as a bot would
We'll use Node.js as our environment, though the matrix-js-sdk can also be used directly in the browser.

Before we start, make sure you have Node.js and NPM installed: follow instructions at for your platform. Then create a new directory to work in:

mkdir my-first-matrix-client cd my-first-matrix-client

Let's write JavaScript

Once you're ready, the first thing to do is install the matrix-js-sdk from NPM:
npm install matrix-js-sdk
We include the SDK in our source exactly as expected:

Login with an access token

Instantiate a new client object and use an access token to login:
<span class="na" style="color: #008080;">baseUrl</span>
<span class="na" style="color: #008080;">accessToken</span>
<span class="na" style="color: #008080;">userId</span>
:""});// note that we use the full MXID for the userId value
(jsdoc for createClient)

If you are logged into Riot, you can find an access token for the logged-in user on the Settings page.

If the homeserver you're logging in to supports logging in with a password, you can also retrieve an access token programmatically using the API. To do this, create a new client with no authentication parameters, then call client.login() with "m.login.password":

<span class="nx">console</span>
In any case, once logged in either with a password or an access token, the client can get the current access token via:
Note: it is essential to keep this access token safe, as it allows complete access to your Matrix account!

Sync and Listen

Next we start the client, this sets up the connection to the server and allows us to begin syncing:
Perform a first sync, and listen for the response, to get the latest state from the homeserver:
<span class="nx">console</span>
.log(state);// state will be 'PREPARED' when the client is ready to use});
Once the sync is complete, we can add listeners for events. We could listen to ALL incoming events, but that would be a lot of traffic, and too much for our simple example. If you want to listen to all events, you can add a listen as follows:
<span class="nx">console</span>
<span class="nx">console</span>
Instead, let's just listen to events happening on the timeline of rooms for which our user is a member:
<span class="nx">console</span>

Access the Store

When we created a new client with sdk.createClient(), an instance of the default store, MatrixInMemoryStore was created and enabled. When we sync, or instruct otherwise our client to fetch data, the data is automatically added to the store.

To access the store, we use accessor methods. For example, to get a list of rooms in which our user is joined:

// client.client.getRooms() returns an array of room objectsvarrooms=client.getRooms();rooms.forEach(room=>{
<span class="nx">console</span>
(jsdoc for client.getRooms)

More usefully, we could get a list of members for each of these rooms:

<span class="kd" style="font-weight: bold;">var</span>
<span class="nx">members</span>
    <span class="nx">console</span>
<span class="p">{'}'});</span>
For each room, we can inspect the timeline in the store:
<span class="nx">room</span>
    <span class="nx">console</span>
<span class="p">{'}'});</span>

Send messages to rooms

To send a message, we create a content object, and specify a room to send to. In this case, I've taken the room ID of, and used it as an example:
<span class="s2" style="color: #d14;">"body"</span>
:"Hello World",
<span class="s2" style="color: #d14;">"msgtype"</span>
:"m.text"};client.sendEvent(testRoomId,"",content,"").then((res)=>{// message sent successfully}).catch((err)=>{
<span class="nx">console</span>
(jsdoc for client.sendEvent)

Knowing this, we can put together message listening and message sending, to build a bot which just echos back any message starting with a "!":

<span class="c1" style="color: #998; font-style: italic;">// we know we only want to respond to messages</span>

<span class="k" style="font-weight: bold;">if</span>
    <span class="k" style="font-weight: bold;">return</span>
<span class="p">{'}'}</span>

<span class="c1" style="color: #998; font-style: italic;">// we are only intested in messages from the test room, which start with "!"</span>

<span class="k" style="font-weight: bold;">if</span>
    <span class="nx">sendNotice</span>
<span class="p">{'}'}</span>
<span class="kd" style="font-weight: bold;">var</span>
    <span class="s2" style="color: #d14;">"body"</span>
    <span class="s2" style="color: #d14;">"msgtype"</span>
<span class="p">{'}'};</span>

<span class="nx">client</span>
    <span class="nx">console</span>
<span class="p">{'}'});</span>
Take a look at the msgtype in the object above. In the previous example, we used "m.text" for this field, but now we're using "m.notice". Bots will often use "m.notice" to differentiate their messages. This allows the client to render notices differently, for example Riot, the most popular client, renders notices with a more pale text colour.


There is much, much more to Matrix, the Client-Server API and the matrix-js-sdk, but this guide should give some understanding of simple usage. In subsequent guides we'll cover more detail and also explore projects you can build on top, such as IoT controls and chatbot interfaces. For now you can take a look at other examples in the matrix-js-sdk itself, and also the Matrix Client-Server API which it implements.

iOS: Welcome to MatrixKit

2015-04-24 — Tutorials — Emmanuel Rohee

Historically we've had two projects for iOS:

  • MatrixSDK: a low level library to interact with a Matrix homeserver
  • Console: an example Matrix client based on MatrixSDK

The primary intention of Console was to demonstrate how to use MatrixSDK to write a Matrix client app. However, this split isn't helpful for developers who want higher level modules that provides UIViewControllers ready to use in an existing app, with no need to manage low level communications with the Matrix homeserver.

It is where the MatrixKit project started. MatrixKit sits between MatrixSDK and your existing iOS app.

It provides customisable UIViewControllers a developer can integrate in their app.  If you want to add to your app a screen to chat in a room, you just need to use the MXKRoomViewController.

We made MatrixKit so that the components it provides are easy to integrate but also easy to customise. We do not have yet full samples of customisation as we've been focused on the library core, but here are a few examples:

MXKRoomViewController  JSQMessagesViewController

You probably recognise the theme of the first one, as it's what we use in the Console app today. The second one is the iOS7-style look and feel from JSQMessagesViewController. With few lines of code we connected it to MatrixKit data models. Yes, data models provided by MatrixKit are reusable too.

MatrixKit is also highly extensible. If you want to create new table cells to render messages, new views, new view controllers, etc, you will find a place to hook them into the MatrixKit code.

MatrixKit repository is here: and it is available via CocoaPods (the MatrixKit pod).

In parallel of MatrixKit, we did some spring-cleaning - the official iOS offerings are now split into three github repos. One for each deliverable:

Other releases:

Today, we released MatrixSDK 0.4.0 (changes). Update your pods :)

Console 0.4.0 (changes) is in the Apple submission process. This will be the first version of the app using MatrixKit. Aesthetically, there is no change since the previous version. The app is more stable due to all the data abstractions and management improvements provided by MatrixKit.

If you're an iOS developer, please have a go with MatrixKit and let us know on how you get on!

Monitoring Synapse Metrics with Prometheus

2015-04-23 — Tutorials — Matthew Hodgson

Note: This blog post is outdated, and an up-to-date tutorial is located on the synapse project repo

Synapse has had support for exporting a comprehensive range of metrics via HTTP since 0.8.1 - we added this to help quantify the benefits of all the performance work which is going on currently in advance of Synapse 0.9. If you're interested in monitoring your own synapse and seeing what's going on using something like Prometheus, Leo just wrote a quick tutorial on getting up and running:

How to monitor Synapse metrics using Prometheus

1: Install prometheus:
Follow instructions at
2: Enable synapse metrics:
Simply setting a (local) port number will enable it. Pick a port. prometheus itself defaults to 9090, so starting just above that for locally monitored services seems reasonable. E.g. 9092:

Add to homeserver.yaml

metrics_port: 9092

Restart synapse

3: Check out synapse-prometheus-config
4: Add synapse.html and synapse.rules
The .html file needs to appear in prometheus's consoles directory, and the .rules file needs to be invoked somewhere in the main config file. A symlink to each from the git checkout into the prometheus directory might be easiest to ensure git pull keeps it updated.
5: Add a prometheus target for synapse
This is easiest if prometheus runs on the same machine as synapse, as it can then just use localhost:
global: {rule_file: "synapse.rules"}

job: {'{'} name: "synapse"

target_group: {'{'} target: "http://localhost:9092/" {'}'} {'}'}

6: Start prometheus:
./prometheus -config.file=prometheus.conf
7: Wait a few seconds for it to start and perform the first scrape,
then visit the console:

And the end result looks something like...

Prometheus screenshot

...amongst many many other system & application metrics.

You can grab the latest version of the tutorial at - thanks to Leo for writing it up. Any questions, let us know!

NextPage 2