Skip to content

Matrix and messaging bridges

Enable the Matrix server (Synapse), its Element web client and mautrix bridges (WhatsApp, Signal, Telegram, Messenger, Discord). For the architecture, see Matrix in DNF.

  1. Declare the services on the target host in etc/config.yaml :

    etc/config.yaml
    services:
    matrix:
    title: "Matrix"
    description: "Messagerie instantanée"
    element: # client web
    turn: # audio/video relay (recommended)
  2. Declare the Matrix administrator (global key, outside hosts) :

    etc/config.yaml
    matrix:
    admin: "alice" # local part of the admin account

    This account administers the bridges and receives supervision alerts.

  3. Create the required secrets (see table below), then deploy.

Authentication goes through SSO Kanidm (OIDC) : every declared user connects with their network account, no Matrix account creation is needed.

To fill in usr/secrets/secrets.yaml :

Sops keyRoleGeneration
matrix-rss-passwordSynapse registration shared secretopenssl rand -hex 32
oidc-secret-matrixOIDC Kanidm client secretsee SSO
turn-secretcoturn shared secret (if turn enabled)openssl rand -hex 32
mautrix-doublepuppet-as-tokendouble puppeting appservice token (shared by bridges)openssl rand -hex 32
mautrix-doublepuppet-hs-tokenSame, required by Synapse, never usedopenssl rand -hex 32

WhatsApp, Signal, Telegram and Messenger bridges are enabled by default with Matrix ; Discord is disabled by default. Per-host setting in the machine’s nix configuration :

# usr/machines/<host>/default.nix
darkone.service.matrix.bridges = {
discord.enable = true; # opt-in
messenger.enable = false; # disable a default bridge
};

Each bridge has its own secrets :

BridgeSops keysNote
WhatsAppmautrix-whatsapp-as-token, mautrix-whatsapp-hs-token, mautrix-whatsapp-encryption-pickle-keyopenssl rand -hex 32 each
Signalmautrix-signal-as-token, mautrix-signal-hs-token, mautrix-signal-encryption-pickle-keyopenssl rand -hex 32 each
Messengermautrix-meta-as-token, mautrix-meta-hs-token, mautrix-meta-encryption-pickle-keyopenssl rand -hex 32 each
Telegrammautrix-telegram-api-id, mautrix-telegram-api-hash, mautrix-telegram-as-token, mautrix-telegram-hs-tokenAPI id/hash to create on my.telegram.org 🡕
Discordmautrix-discord-as-token, mautrix-discord-hs-tokenopenssl rand -hex 32 each

The as/hs tokens make each bridge’s appservice registration deterministic (see appservice tokens).

Nothing to manage : any local account (@*:domain.tld) can use each bridge with their own remote account, without interference between users. The matrix.admin account has the bot administration commands (help in direct conversation with the bot for the list).

Bridges share a doublepuppet appservice (token mautrix-doublepuppet-as-token) : messages sent from the phone appear in Matrix as coming from the user’s real account. This is automatic for all local accounts, no action per user.

ActionCommand
Service statussystemctl status matrix-synapse mautrix-whatsapp mautrix-signal mautrix-telegram mautrix-meta-messenger
Bridge logsjournalctl -u mautrix-whatsapp -f
Synapse logsjournalctl -u matrix-synapse -f

A bot that does not respond to help while its service is running almost always falls into one of two cases : its tokens no longer match the registration loaded by Synapse, or it receives messages but cannot decrypt them. Diagnosis in order :

  1. Is the service actually running ?

    Fenêtre de terminal
    systemctl status mautrix-<bridge>
    journalctl -u mautrix-<bridge> -n 50

    If it keeps restarting with « The as_token was not accepted », Synapse’s in-memory registration no longer matches the bridge’s tokens (typical after a reset or migration, see the callout above) : systemctl restart matrix-synapse, then restart the bridge.

  2. Does the message reach the bridge ? Follow Synapse while sending a help to the bot :

    Fenêtre de terminal
    journalctl -u matrix-synapse -f | grep transactions

    A line PUT .../_matrix/app/v1/transactions/... 200 should appear. If nothing goes out, the appservice is not loaded : check for the registration file in /var/lib/mautrix-<bridge>/ and restart Synapse.

  3. Can the bridge decrypt it ? If the transaction goes through (200) but the bot remains mute, follow its logs during a new message :

    Fenêtre de terminal
    journalctl -u mautrix-<bridge> -f

    Look for decrypt, session, verification, dropping. A decryption failure means the room session key was not shared with the bot’s current device (classic case after a bridge reset : its e2ee identity has changed).

  4. Recreate the conversation. Leave the direct message with the bot and start a new one : the client establishes a fresh encryption session and shares the key with the bot’s current device. Check along the way that your own Element session is verified.

  5. Last resort : reset the bridge state (callout below), then restart Synapse to reload the registration.