- TypeScript 96.4%
- Just 1.3%
- JavaScript 1.3%
- Dockerfile 1%
|
|
||
|---|---|---|
| bot | ||
| docs/superpowers | ||
| scripts | ||
| workflows | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Justfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
n8n Matrix Bridge
Matrix E2EE relay bot for n8n. It listens to encrypted Matrix rooms, detects mentions of the bot, forwards the mention payload to an n8n Webhook, and lets n8n send encrypted replies back through a local /send endpoint.
n8n does not need Matrix credentials or custom nodes; it only uses built-in Webhook and HTTP Request nodes.
Quickstart
-
Create a dedicated Matrix bot user/device and get an access token.
-
Invite the bot to the encrypted Matrix rooms you want to bridge.
-
Import the reference n8n workflow:
workflows/matrix-mention.json
Docker Compose
-
Create the environment file:
cp bot/.env.example .env -
Edit
.envand set your Matrix credentials plus a strongBOT_SEND_TOKEN. -
Start n8n and the bot:
docker compose up -d -
Open n8n at:
http://localhost:5678 -
In Matrix, mention the bot. n8n should receive the webhook payload and can reply through
http://bot:8080/send.
Without Docker
-
Install and build the bot:
cd bot npm install npm run build cd .. -
Set the required environment variables. For example:
export MATRIX_HOMESERVER_URL='https://matrix.org' export MATRIX_ACCESS_TOKEN='syt_xxxx' export MATRIX_BOT_USER_ID='@mybot:matrix.org' export MATRIX_STORAGE_DIR='./bot-storage' export N8N_WEBHOOK_URL='http://localhost:5678/webhook/matrix-mention' export BOT_SEND_TOKEN='change-me-strong-secret' -
Start the bot:
cd bot npm start -
Run n8n separately and point its HTTP Request reply node at the bot:
http://localhost:8080/sendIf n8n runs in Docker while the bot runs on the host, use:
http://host.docker.internal:8080/send -
In Matrix, mention the bot. n8n should receive the webhook payload and can reply through
/send.
Configuration
Required variables, usually in .env:
MATRIX_HOMESERVER_URL=https://matrix.org
MATRIX_ACCESS_TOKEN=syt_xxxx
MATRIX_BOT_USER_ID=@mybot:matrix.org
MATRIX_STORAGE_DIR=/data/storage
N8N_WEBHOOK_URL=http://n8n:5678/webhook/matrix-mention
BOT_SEND_TOKEN=change-me-strong-secret
Optional variables:
MATRIX_ROOM_ALLOWLIST=!roomid:matrix.org,!other:matrix.org
WEBHOOK_AUTH_TOKEN=n8n-webhook-secret
BOT_SEND_PORT=8080
BOT_SEND_BIND_ADDRESS=127.0.0.1
Notes:
MATRIX_STORAGE_DIRstores the bot's crypto database and sync token. Do not delete it unless you intend to reset the bot device.MATRIX_ROOM_ALLOWLISTrestricts which rooms can trigger n8n. Leave it unset to allow all rooms the bot is in.- If
WEBHOOK_AUTH_TOKENis set, outbound webhook requests includeAuthorization: Bearer <WEBHOOK_AUTH_TOKEN>. BOT_SEND_BIND_ADDRESScontrols where/sendlistens. It defaults to127.0.0.1for host-local only; use0.0.0.0to listen on every interface./sendauthentication uses the raw token:Authorization: <BOT_SEND_TOKEN>.
n8n
Import workflows/matrix-mention.json or create a workflow with:
-
Webhook node
- Method:
POST - Path:
matrix-mention - Production URL should match
N8N_WEBHOOK_URL.
- Method:
-
Your processing nodes.
-
HTTP Request node to send replies:
-
Method:
POST -
URL when using the provided Docker Compose network:
http://bot:8080/send -
Header:
Authorization: <BOT_SEND_TOKEN> -
JSON/body fields:
{ "roomId": "={{ $json.body.roomId }}", "message": "Hello from n8n!", "replyToEventId": "={{ $json.body.eventId }}", "replyToSenderId": "={{ $json.body.senderId }}", "replyToBody": "={{ $json.body.originalContent }}" }
-
/send request body
/send accepts POST requests with a JSON object body. The Authorization header must be exactly the raw BOT_SEND_TOKEN value, without a Bearer prefix.
Required fields:
roomId— Matrix room ID to send into, for example!room:server.message— reply text to send.
Optional fields:
replyToEventId— Matrix event ID to reply to. If set, the bot addsm.relates_to.m.in_reply_to.replyToSenderId— sender MXID for the quoted fallback reply text, for example@user:server.replyToBody— original message body to quote in the rich reply fallback.asNotice— boolean. Iftrue, sendsmsgtype: m.notice; otherwise sendsm.text.
Minimal request:
{
"roomId": "!room:server",
"message": "Hello from n8n!"
}
Reply request using the inbound webhook payload:
{
"roomId": "={{ $json.body.roomId }}",
"message": "Hello from n8n!",
"replyToEventId": "={{ $json.body.eventId }}",
"replyToSenderId": "={{ $json.body.senderId }}",
"replyToBody": "={{ $json.body.originalContent }}",
"asNotice": false
}
Behavior notes:
- Request body size is limited to 64 KiB.
- Invalid JSON, missing
roomId, or missingmessagereturns400. - Unauthorized requests return
401. - Successful sends return JSON like
{ "event_id": "$event:server" }.
The Webhook node exposes the bot payload under $json.body:
{
"text": "cleaned mention text",
"originalContent": "original Matrix body",
"roomId": "!room:server",
"eventId": "$event:server",
"senderId": "@user:server",
"timestamp": 1234567890,
"formattedBody": null
}
Bot verification info
The bridge exposes a small authenticated helper endpoint for out-of-band device verification:
GET /verify-info
Authorization: <BOT_SEND_TOKEN>
Example:
curl -H "Authorization: $BOT_SEND_TOKEN" http://localhost:8080/verify-info
It returns the bot MXID, device ID, display name, and Matrix device keys. Compare the deviceId and ed25519Fingerprint over a trusted channel with what your Matrix client shows for the bot device, then mark the device as verified in your Matrix client.
When using Docker Compose, run the curl from inside the n8n/bot network or temporarily expose the bot port. The provided Compose file binds /send and /verify-info to 0.0.0.0 inside the container but does not publish the port to the host.
Development
cd bot
npm install
npm run build
npm test