Matrix, Mattermost & Nextcloud Talk
Self-hosted workspace chat platforms, including Matrix E2EE.
This page is the reference for Revka’s three self-hosted workspace-chat channels: Matrix (including end-to-end encrypted rooms), Mattermost, and Nextcloud Talk. All three keep your agent’s conversation history inside infrastructure you control, which makes them a good fit for private, regulated, or air-gapped deployments. Use this page when you need exact config keys, delivery behavior, build requirements, and the webhook signature contract. For step-by-step setup, see Set up Mattermost & Nextcloud Talk and Set up Telegram & Matrix.
All three channels are configured under [channels_config] in ~/.revka/config.toml. Edit the file directly or run revka onboard --channels-only, then apply changes by restarting the daemon. For the trait model, delivery modes, and allowlist semantics shared by every channel, see Channels overview.
At a glance
Section titled “At a glance”| Channel | Receive mode | Public inbound port? | Build feature flag |
|---|---|---|---|
| Matrix | sync API (E2EE) | No | channel-matrix |
| Mattermost | REST v4 polling | No | — |
| Nextcloud Talk | webhook (push) | Yes | — |
Matrix is the only one of the three that is feature-gated at build time. Mattermost and Nextcloud Talk are always compiled in.
Matrix
Section titled “Matrix”The Matrix channel uses the Matrix Client-Server API via matrix-sdk. It supports end-to-end encryption (E2EE) for encrypted rooms, multiple allowed rooms, room-alias resolution, draft streaming, multi-message paragraph delivery, voice transcription of audio attachments, one-time-key (OTK) conflict detection, and cross-signing recovery from server-side key backup.
Configure Matrix
Section titled “Configure Matrix”[channels_config.matrix]homeserver = "https://matrix.example.com"access_token = "syt_..."user_id = "@revka:matrix.example.com"device_id = "DEVICEID123"room_id = "!room:matrix.example.com"allowed_rooms = []allowed_users = ["*"]stream_mode = "partial" # off | partial | multi_messagedraft_update_interval_ms = 1500multi_message_delay_ms = 800recovery_key = "" # optional: cross-signing recovery| Field | Type | Default | Meaning |
|---|---|---|---|
homeserver | string | — | Homeserver base URL. Required. |
access_token | string | — | Bot account access token. Required. |
user_id | string | — | Bot Matrix ID (@name:server). Recommended for E2EE session restore. |
device_id | string | — | Stable device identifier. Recommended for E2EE; without it a new device is registered on every restart, breaking key sharing. |
room_id | string | — | Primary room. Canonical ID (!room:server) is preferred; an alias (#name:server) is resolved at runtime. |
allowed_rooms | array | [] | When non-empty, the full set of room IDs/aliases the bot accepts messages from (this replaces the single-room filter; include the primary room_id here if you still want it accepted). Empty (default) = listen only on the configured room_id. |
allowed_users | array | — | Matrix user IDs allowed to interact. [] denies all; ["*"] allows everyone. |
stream_mode | string | — | "off", "partial" (live-edits a draft via m.replace), or "multi_message" (paragraph splits). |
draft_update_interval_ms | number | 1500 | Edit throttle for draft streaming. Higher than other channels to absorb E2EE overhead. |
multi_message_delay_ms | number | 800 | Delay between paragraph chunks in multi_message mode. |
recovery_key | string | — | Cross-signing recovery key for restoring room keys from server backup. |
transcription.* | table | — | Voice-to-text for audio attachments. See Voice, TTS & transcription. |
The Matrix channel does not need a public inbound port — it connects out to the homeserver and receives messages over the sync API.
End-to-end encryption (E2EE)
Section titled “End-to-end encryption (E2EE)”E2EE is transparent through matrix-sdk: the channel decrypts and replies in encrypted rooms without extra configuration, provided the bot’s device has the room keys. For reliable encrypted operation:
- Set a stable
device_id. A drifting device ID forces a fresh device registration on every restart, which breaks existing key sharing and device verification. Get the value from/_matrix/client/v3/account/whoami, from a password login, or from Element’s Settings → Sessions. - Set
user_idso identity survives restarts. - Set a
recovery_keyso room keys and cross-signing secrets are restored automatically from server-side backup after a crypto-store reset, fresh install, or device reset. On success Revka logsMatrix E2EE recovery successful.
The local crypto store lives at ~/.revka/state/matrix/. Do not delete it without first deregistering the device on the homeserver — a stale device causes an OTK upload conflict.
”Connected but no reply” checklist
Section titled “”Connected but no reply” checklist”The most common Matrix symptom is “configured correctly, checks pass, but the bot does not respond.” Validate in order:
- The sender is allowed by
allowed_users(use["*"]for testing —[]denies all). - The bot account has actually joined the target room.
- The
access_tokenbelongs to the same bot account (whoamicheck). - In E2EE rooms, the bot device has received room keys from a trusted device.
- The daemon was restarted after config changes.
For E2EE diagnostics, raise the log level for the channel:
RUST_LOG=revka::channels::matrix=debug revka daemonAdd matrix_sdk_crypto=debug for SDK-level crypto detail.
Mattermost
Section titled “Mattermost”The Mattermost channel polls the Mattermost REST API v4 with a bot access token. It needs no public inbound port. It supports threaded replies, mention-only filtering, typing indicators, and voice transcription of audio attachments (25 MB limit).
Configure Mattermost
Section titled “Configure Mattermost”[channels_config.mattermost]url = "https://mm.example.com"bot_token = "mattermost-token"channel_id = "channel-id"allowed_users = ["*"]thread_replies = truemention_only = false| Field | Type | Default | Meaning |
|---|---|---|---|
url | string | — | Base URL of your Mattermost server. Required. |
bot_token | string | — | Bot account personal access token. Required. |
channel_id | string | — | Channel to listen to. Required for listen mode; omit to listen on all accessible channels. |
allowed_users | array | — | Mattermost user IDs allowed to interact. [] denies all; ["*"] allows everyone. |
thread_replies | bool | true | Reply to top-level messages in a thread on that post. |
mention_only | bool | false | When true, only process messages that @-mention the bot. |
proxy_url | string | — | Optional per-channel HTTP/SOCKS5 proxy. |
transcription.* | table | — | Voice-to-text for audio attachments (25 MB limit). |
Threading and mention-only behavior
Section titled “Threading and mention-only behavior”- If a user replies inside an existing thread, Revka always replies in that same thread.
- With
thread_replies = true(the default), Revka answers a top-level message by threading on it. Withthread_replies = false, it answers at the channel root. - With
mention_only = true, Revka applies an extra filter after theallowed_userscheck: messages without an explicit@bot_usernamemention are ignored, and the mention token is stripped before the text reaches the model. This is useful in busy shared channels to cut unnecessary model calls.
Polling is rate-limited with retry backoff. To create the bot account and find the channel ID, follow Set up Mattermost & Nextcloud Talk.
Nextcloud Talk
Section titled “Nextcloud Talk”The Nextcloud Talk channel runs in webhook mode. Nextcloud pushes each room event to your gateway at POST /nextcloud-talk; Revka verifies the signature, runs the agent, and replies through the Nextcloud Talk OCS API. Because Nextcloud must reach your gateway, you need a public HTTPS URL — see Expose your gateway with a tunnel.
Configure Nextcloud Talk
Section titled “Configure Nextcloud Talk”[channels_config.nextcloud_talk]base_url = "https://cloud.example.com"app_token = "nextcloud-talk-app-token"bot_name = "revka"webhook_secret = "optional-webhook-secret"allowed_users = ["*"]| Field | Type | Default | Meaning |
|---|---|---|---|
base_url | string | — | Your Nextcloud instance URL. Required. A trailing slash is stripped automatically. |
app_token | string | — | Bot app token, sent as Authorization: Bearer <token> on outbound OCS replies. Required. |
bot_name | string | — | The bot’s Talk display name. Used for self-echo prevention (see below). When unset, the field defaults to an empty string; however, the literal name revka is always treated as the bot via a hardcoded check regardless of this setting. |
webhook_secret | string | — | Shared HMAC secret for verifying inbound webhook signatures. Recommended. Overridden by the REVKA_NEXTCLOUD_TALK_WEBHOOK_SECRET environment variable. |
allowed_users | array | — | Allowed Nextcloud actor IDs. [] denies all; ["*"] allows everyone. Matched after stripping any users/ or bots/ prefix. |
proxy_url | string | — | Optional per-channel HTTP/SOCKS5 proxy. |
Webhook ingress endpoint
Section titled “Webhook ingress endpoint”Point your Nextcloud Talk bot’s webhook URL at the gateway ingress path. The gateway logs the route on startup (POST /nextcloud-talk — Nextcloud Talk bot webhook).
POST /nextcloud-talkContent-Type: application/jsonX-Nextcloud-Talk-Random: <random-seed>X-Nextcloud-Talk-Signature: <hmac-hex>This endpoint carries no bearer token — it is authenticated by the signature instead (see below).
| Response | When |
|---|---|
404 | [channels_config.nextcloud_talk] is not configured. |
400 | The request body is not valid JSON. |
401 | Signature verification failed (only checked when a secret is set). |
200 | Accepted. Also returned when the payload contains no actionable user message — bot, system, non-Note, and non-allowed events are acknowledged silently. |
Outbound replies go to the Talk room from the webhook payload via the OCS chat API:
POST <base_url>/ocs/v2.php/apps/spreed/api/v1/chat/<room-token>?format=jsonAuthorization: Bearer <app_token>OCS-APIRequest: trueRevka parses both the Activity Streams 2.0 Create payload that current Nextcloud Talk bots send and the legacy message format, and it normalizes both millisecond and second timestamps.
bot_name self-echo prevention
Section titled “bot_name self-echo prevention”A webhook bot can see its own replies echoed back as new room events, creating a feedback loop where the agent answers itself forever. Revka drops self-originated events before they reach the model:
- Events whose actor type is
botsorapplicationare ignored. - Events whose actor ID carries the
bots/prefix are ignored. - Events whose actor name matches the configured
bot_name(case-insensitive) are ignored. The literal namerevkais always treated as the bot, even if you set a differentbot_name.
The name check is the safety net: Nextcloud does not always set the actor type reliably for bot-sent messages, so matching on bot_name catches echoes that slip through the type check. Set bot_name to your bot’s exact Talk display name.
Webhook signature verification
Section titled “Webhook signature verification”The Nextcloud Talk webhook is the one ingress endpoint among these three channels (Matrix and Mattermost are outbound-connecting, so they have no inbound webhook to sign). When webhook_secret is configured, every inbound request to POST /nextcloud-talk is authenticated by an HMAC-SHA256 signature.
Revka reads two headers and computes:
hex( hmac_sha256( secret, X-Nextcloud-Talk-Random + raw_request_body ) )The result is compared in constant time against the X-Nextcloud-Talk-Signature header. Key details:
- The HMAC is over the raw request body concatenated after the random seed — not a re-serialized JSON object. Do not let any proxy rewrite or reformat the body between Nextcloud and the gateway, or verification will fail.
- A leading
sha256=prefix on the signature header is accepted and stripped. - If the
X-Nextcloud-Talk-Randomheader is missing, the signature is malformed (not valid hex), or the digest does not match, the gateway rejects the request with401 Invalid signature.
REVKA_NEXTCLOUD_TALK_WEBHOOK_SECRET
Section titled “REVKA_NEXTCLOUD_TALK_WEBHOOK_SECRET”The secret can come from config or the environment, with the environment taking priority:
export REVKA_NEXTCLOUD_TALK_WEBHOOK_SECRET="your-webhook-secret"Resolution order:
REVKA_NEXTCLOUD_TALK_WEBHOOK_SECRET(environment) — used when set and non-empty.webhook_secretunder[channels_config.nextcloud_talk]— used otherwise.
Use the environment variable to keep the secret out of config.toml. The same secret must be set on the Nextcloud Talk bot so both sides compute the same HMAC. For the full set of gateway ingress endpoints and their signing rules, see Webhook ingress.
The channel-matrix feature flag
Section titled “The channel-matrix feature flag”Matrix support is compiled behind the Cargo feature channel-matrix, which pulls in the matrix-sdk dependency. This is not a default Cargo feature — a plain cargo build or cargo install omits Matrix. Official release binaries include it because the release pipeline explicitly builds with --features channel-matrix. To build it yourself:
cargo build --release --features channel-matrixIf you build with --no-default-features and a hand-picked feature list, add channel-matrix back when you want Matrix. Mattermost and Nextcloud Talk have no feature flag and are always available. For the complete list of channel feature flags (Matrix, Lark, Nostr, WhatsApp Web, Voice Wake), see Cargo feature flags & ADRs.
Allowlist semantics
Section titled “Allowlist semantics”All three channels gate inbound senders with allowed_users:
- An empty list (
[]) denies all senders. - A single
"*"entry allows all senders. - Otherwise, only listed IDs are accepted — Matrix user IDs, Mattermost user IDs, or Nextcloud actor IDs respectively.
Start with allowed_users = ["*"] to confirm delivery, then tighten to explicit IDs. For Nextcloud Talk, actor IDs are matched after the users/ / bots/ prefix is stripped.
Related pages
Section titled “Related pages”- Set up Mattermost & Nextcloud Talk — step-by-step bot creation and webhook registration.
- Set up Telegram & Matrix — Matrix setup, including the E2EE guide.
- Channels overview — the channel trait, delivery modes, and allowlist model.
- Webhook ingress — all gateway webhook endpoints and request signing.
- Expose your gateway with a tunnel — give the Nextcloud Talk webhook a public HTTPS URL.
- Voice, TTS & transcription — audio-attachment transcription used by these channels.
- Cargo feature flags & ADRs — the
channel-matrixflag and other build options.