Nextcloud Talk
Nextcloud Talk integration via the Talk Bot webhook protocol. Self-hosted, federated, and E2E-capable: another sovereign-communication option alongside Matrix and Mattermost.
Who can talk to the agent
Inbound senders are gated against the peer set resolved for the bound
agent, drawn from the peer_groups config the agent belongs to. Matching strips
a leading @ and is case-insensitive against the channel’s native sender
identifier. An empty set denies everyone; a set containing "*" accepts
anyone; otherwise only the listed external peers (and peer agents) are accepted.
This is separate from gateway pairing (gateway.require_pairing), which
authenticates HTTP/WebSocket clients, not chat-channel senders.
A peer group for nextcloud sets channel to nextcloud, lists the allowed senders in
external_peers (for nextcloud, the Nextcloud actor ID; ["*"] accepts anyone), optionally
names peer agents for cross-agent dispatch, an ignore blocklist, and an
output_modality (mirror, voice, or text). See Peer Groups
for the field reference.
Where to set this:
Gateway dashboard
Open /config/peer_groups in the web dashboard.
zerocode
In the Config pane, under Peer groups.
What this integration does
- Receives inbound Talk events via
POST /nextcloud-talk/<alias>on the gateway (bare/nextcloud-talkstill works as a deprecated fallback) - Requires and verifies webhook signatures (HMAC-SHA256) with the installed bot secret
- Sends replies back to Talk rooms via the signed Nextcloud Talk Bot API
Prerequisites
-
Nextcloud server 27.1 or later with Talk 17.1 or later. This is a hard minimum, not a recommendation: the signed Talk Bot API this integration uses to send replies was introduced in Talk 17.1, and
occ talk:bot:installbelow is unavailable on earlier releases. -
Bot installed with both the
webhookandresponsefeatures, which let Nextcloud deliver room messages to ZeroClaw and let ZeroClaw send replies:sudo -u www-data php occ talk:bot:install \ -f webhook -f response \ zeroclaw-bot '<shared-secret>' \ 'https://<your-public-url>/nextcloud-talk/<alias>' -
Bot secret from that installation. Nextcloud issues one shared secret per bot, used both to verify inbound webhook signatures and to sign outbound bot-API replies. Set it as
webhook_secret, which is canonical.bot_tokenis a deprecated alias for the same value: if both are set they must be identical. It cannot hold a different outbound secret. Conflicting non-empty values are not silently resolved in favour of one; the conflict is logged and the alias resolves to no secret, so the channel then behaves exactly as if unconfigured: inbound401, no outbound send. -
Publicly-reachable gateway: see Setup → Container for tunnel options if self-hosted
Both directions fail closed on a missing secret, and there is no unauthenticated mode:
- Inbound: signature verification is mandatory. With no resolved secret the
webhook endpoint returns
401and never reaches the agent. There is no “public” mode that accepts unverified webhooks. - Outbound: no request is sent at all, so misconfiguration never puts an unsigned or wrongly-signed request on the wire.
Upgrading is a breaking change. A deployment that previously ran without a secret accepted webhooks; it now rejects every one of them with
401. Install the bot withocc talk:bot:install, then set that secret aswebhook_secretbefore upgrading, or inbound messages stop being processed.
Configuration
app_token 🔑
Deprecated, unused. Nextcloud Talk sends do not authenticate via OCS bearer auth (see webhook_secret); this field is only accepted so existing configs that set it don’t fail to parse. Safe to remove from your config.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.app_token field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.app_token field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.app_token # masked input, stored encrypted
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__app_token=
base_url*
Nextcloud base URL (e.g. "https://cloud.example.com").
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.base_url field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.base_url field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.base_url <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__base_url=
bot_name
Display name of the bot in Nextcloud Talk (e.g. “zeroclaw”). Used to filter out the bot’s own messages and prevent feedback loops. If not set, defaults to an empty string (no self-message filtering by name).
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.bot_name field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.bot_name field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.bot_name <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__bot_name=
bot_token 🔑
DEPRECATED alias for webhook_secret, kept for migration. Nextcloud issues ONE secret per installed bot and uses it for both directions, so this cannot hold a different outbound secret. When both are set to different non-empty values the channel logs the conflict and fails closed as unconfigured: inbound 401 and no outbound send. Prefer webhook_secret; this alias will be removed. Upgrade from a bot_token-only configuration: copy the installed bot secret to webhook_secret, verify replies still send, then delete bot_token.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.bot_token field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.bot_token field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.bot_token # masked input, stored encrypted
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__bot_token=
draft_update_interval_ms
Retained for configuration compatibility. Currently inert while draft updates are disabled for this channel. Default: 1000 ms.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.draft_update_interval_ms field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.draft_update_interval_ms field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.draft_update_interval_ms <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__draft_update_interval_ms=
excluded_tools
Tools excluded from this channel’s tool spec. When set, these tools are not exposed to the model when responding via this channel.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.excluded_tools field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.excluded_tools field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.excluded_tools <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__excluded_tools=
proxy_url
Per-channel proxy URL (http, https, socks5, socks5h). Overrides the global [proxy] setting for this channel only.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.proxy_url field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.proxy_url field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.proxy_url <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__proxy_url=
stream_mode
Retained for configuration compatibility. Nextcloud Talk’s bot API does not provide message IDs or edit/delete operations, so draft updates are disabled and responses are currently sent as one final message for every value.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.stream_mode field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.stream_mode field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.stream_mode <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__stream_mode=
webhook_secret 🔑
The bot secret Nextcloud installed for this bot. Canonical field. Used for BOTH directions: verifying inbound webhook signatures and signing outbound bot-API requests. When it is unset, inbound webhooks are rejected and no outbound request is sent (fail closed). Can also be set via ZEROCLAW_NEXTCLOUD_TALK_WEBHOOK_SECRET.
Set it on any surface:
Gateway dashboard
Open /config/channels/nextcloud_talk and set the channels.nextcloud_talk.<alias>.webhook_secret field.
zerocode
In the Config pane, set the channels.nextcloud_talk.<alias>.webhook_secret field.
zeroclaw config
zeroclaw config set channels.nextcloud_talk.<alias>.webhook_secret # masked input, stored encrypted
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__nextcloud_talk__<alias>__webhook_secret=
The channel is read from the default alias. Set it through any config surface:
Gateway dashboard
Open /config/channels/nextcloud_talk in the web dashboard.
zerocode
In the Config pane, under Channels.
webhook_secret can also be supplied at runtime via the generic env override ZEROCLAW_channels__nextcloud_talk__default__webhook_secret, useful for rotating it without editing the config.
app_token is deprecated and unused (replies no longer go through OCS bearer auth); it’s only still accepted so old configs that set it don’t fail to parse.
Gateway endpoint
sh
zeroclaw daemon
Configure your Talk bot’s webhook URL to point at the alias of the
[channels.nextcloud_talk.<alias>] instance that should receive it:
https://<your-public-url>/nextcloud-talk/<alias>
For example, [channels.nextcloud_talk.work] receives POST /nextcloud-talk/work.
This per-alias routing (#6312) lets you run several Talk bots side by side and
deliver each one’s webhooks to the right instance.
The bare https://<your-public-url>/nextcloud-talk path still works but is
deprecated: it resolves to the lexicographically-first alias (deterministic
across restarts) and returns an X-Zeroclaw-Deprecation response header.
Single-instance deployments can keep using it unchanged. An unknown alias returns 404.
Local development? Configure [tunnel] in your config (ngrok, Cloudflare, or Tailscale) and the gateway exposes itself on startup: see Operations → Network deployment.
Signature verification
Inbound requests must carry:
X-Nextcloud-Talk-RandomheaderX-Nextcloud-Talk-Signatureheader
ZeroClaw verifies:
expected_sig = hex(hmac_sha256(secret, random + raw_request_body))
if X-Nextcloud-Talk-Signature != expected_sig:
return 401
Without a resolved secret, ZeroClaw returns 401 before parsing or dispatching
the webhook. There is no mode that accepts an unverified request.
Message routing
- Bot-originated events (
actorType = "bots") are ignored: prevents feedback loops - System events (joins, leaves, membership changes) are ignored
- Non-message events are ignored
- User messages are dispatched to the agent loop
- Replies go back to the originating room via the
tokenin the webhook payload
Quick validation
- Set
external_peers = ["*"]in the peer group for first-time testing - Send a test message in the configured Talk room
- Confirm ZeroClaw receives and replies in the same room
- Tighten the peer group to explicit actor IDs (e.g.
["alice", "bob"])
Troubleshooting
404 Nextcloud Talk not configured:[channels.nextcloud_talk.default]section missing orenabled = false401 Invalid signature: secret mismatch, wrong random header, or body-signing bug. Check the raw body is being signed (not the parsed JSON)- No reply, webhook
200: event was filtered. Check logs for “actorType = bots” or a sender not in the peer set - Replies delivered but look wrong: check thread context; Talk replies are currently root-level only
Streaming
Nextcloud Talk does not support message edits via the Bot API, so streaming draft updates are disabled for this channel. Replies are sent on stream completion only.
Self-hosting notes
- TLS: terminate at your reverse proxy; webhook signature verification works over HTTP-to-container loopback
- Outbound replies authenticate via the Bot API’s HMAC signature (
webhook_secret/bot_token), not a bearer token; there is no separate OCS bearer credential to manage - Rate limits are Nextcloud-server dependent; the default bot doesn’t run into them in normal conversation cadences
- Per-channel proxy: set
proxy_urlto override the global[proxy]setting for Nextcloud Talk only (http://,https://,socks5://,socks5h://)
See also
- Matrix: richer E2EE but more operational complexity
- Mattermost: similar self-hosted posture, different protocol
- Channels → Overview