Matrix bridge reference
Reference for Claire’s Synapse and mautrix bridge integration.
Claire bridges every messaging network through Matrix. This is the working reference for the mautrix APIs it depends on; the upstream documentation lives at docs.mau.fi.
Bridge bot commands#
Each bridge has a bot user that accepts commands in a control room — a direct message with the bot.
WhatsApp#
Bot: @whatsappbot:claire.local
| Command | Effect |
|---|---|
login qr | Start the QR code login flow |
login phone | Start the phone pairing-code flow |
logout | Disconnect WhatsApp |
ping | Check connection status |
help | List available commands |
After a successful login the bridge does three things, in order:
- Sends
Successfully logged in as +<phone>as anm.notice. - Creates portal rooms for each WhatsApp chat, which takes about a minute.
- Backfills the 50 most recent messages per room, configurably.
Telegram#
Bot: @telegrambot:claire.local. login prompts for a phone number and then a verification code; logout and ping behave as above.
Instagram#
Bot: @instagrambot:claire.local, with login-cookie to authenticate. The bridge is the Meta bridge, so it uses the meta_ prefix; set network.mode for Instagram DMs.
Ghost user patterns#
Ghost users represent remote platform contacts inside Matrix. Their IDs follow @<prefix><platform_id>:<server_name>.
| Platform | Prefix | Example |
|---|---|---|
whatsapp_ | @whatsapp_15551234567:claire.local | |
| Telegram | _telegram_ | @_telegram_123456789:claire.local |
meta_ | @meta_987654321:claire.local | |
| iMessage | _imessage_ | @_imessage_+15551234567:claire.local |
The self ghost user#
Without double puppeting, a user’s own messages arrive from their ghost user rather than from the bot. If the linked WhatsApp number is +15551234567, outgoing messages come from @whatsapp_15551234567:claire.local. The server parses the number out of the login success message and tracks it as the session’s self ghost ID, so isFromMe is set correctly.
Double puppeting#
Enabled in Claire, gated behind ENABLE_DOUBLE_PUPPETING=true in the server environment. With it active, messages a user sends from their phone appear as their actual Matrix account instead of their ghost, the server tracks matrixUserId per session, and locally sent event IDs are tracked to prevent echo loops.
bridge:
double_puppet:
secrets:
claire.local: "as_token:<bridge_as_token>"Backfill behaviour#
Backfill is constrained by Matrix itself, and the constraints surprise people, so they are worth stating plainly:
- Matrix does not support inserting messages into room history.
- Backfilled messages land at the end of the timeline regardless of their original timestamp.
- Historical backfill only works in new, empty rooms.
- WhatsApp uses one-time history-sync blobs sent after device linking.
- MSC2716, which would have allowed true history insertion, was abandoned.
Appservice registration#
Each bridge needs a registration file referenced from Synapse’s homeserver.yaml.
app_service_config_files:
- /data/whatsapp-registration.yaml
- /data/telegram-registration.yaml
- /data/instagram-registration.yaml| Field | Purpose |
|---|---|
as_token / hs_token | Authentication between the bridge and Synapse. |
username_template | Controls the ghost user ID format. Must match GHOST_USER_PREFIXES. |
bot_username | The bridge bot user ID. |
Troubleshooting#
| Symptom | Likely cause |
|---|---|
| Bot not responding | Appservice connectivity — check docker logs claire-synapse. |
| No messages bridged | Bridge is not logged in. Send ping in the control room. |
| Login loop | WhatsApp disconnects linked devices after the phone is offline for more than two weeks. |
| “User not found” | Ghost prefix mismatch between the bridge config and types.ts. |
docker logs claire-mautrix-whatsapp -f
docker logs claire-mautrix-telegram -f
docker logs claire-mautrix-instagram -f
docker logs claire-synapse -f