Nextcloud Talk channel plugin: setup and capabilities
Covers the Nextcloud Talk channel plugin for OpenClaw, including install steps, supported features like DMs, rooms, reactions, and file shares, and how messages reach the agent. For self-hosted Nextcloud admins and OpenClaw operators.
Read this when
- Working on Nextcloud Talk channel features
Nextcloud Talk is a channel plugin you can install (@openclaw/nextcloud-talk). It links OpenClaw with a Nextcloud instance you host yourself, using a Talk webhook bot. Supported features include direct messages, rooms, reactions, markdown messages, and Talk file shares in both directions.
Install
openclaw plugins install @openclaw/nextcloud-talk
To track the current official release tag, install with the bare package spec. Pin an exact version only when you need a reproducible install.
Inbound attachments need a host that provides runtime.channel.media.deleteMediaBuffer. The fact that a source checkout passes tests does not prove an already published host provides this API. Use the coordinated host/plugin release qualified below, not a standalone build of this plugin with its historical compatibility metadata.
From a local checkout (dev workflows):
openclaw plugins install ./path/to/local/nextcloud-talk-plugin
After installing, check the application result.
How messages reach the agent
sequenceDiagram
participant User as Talk user
participant Talk as Nextcloud Talk
participant Hook as OpenClaw webhook
participant Agent as OpenClaw agent
User->>Talk: Send a message
Talk->>Hook: Signed webhook
Hook->>Hook: Verify signature and store the message durably
Hook-->>Talk: HTTP 200 with durable acceptance marker
Hook->>Hook: Check sender, room, and mention access
alt Message is allowed
Hook->>Agent: Route to the agent session
Agent-->>Talk: Reply through the Talk API
Talk-->>User: Show the reply
else Message is not allowed
Note over Hook: No agent turn
end
A webhook acknowledgement confirms durable receipt. It does not grant permission to run an agent, nor does it mean the reply is complete. Access checks still apply after acceptance. Unknown DM senders may receive a pairing code instead of an agent reply. Nextcloud must be able to reach the webhook URL. Replies travel back through the Talk API, not in the webhook response.
Quick setup (beginner)
-
Install the plugin (above).
-
Expose the Gateway webhook route
/nextcloud-talk-webhookthrough your HTTPS reverse proxy, forwarding to the Gateway port (default18789). On your Nextcloud server, create a bot using that public URL:./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature webhook --feature response --feature reactionKeep
--feature response: without it, outbound replies fail with 401. Repair an existing bot with./occ talk:bot:state --feature webhook --feature response --feature reaction <botId> 1. -
Enable the bot in the target room settings.
-
Configure OpenClaw:
- Config:
channels.nextcloud-talk.baseUrl+channels.nextcloud-talk.botSecret - Or env:
NEXTCLOUD_TALK_BOT_SECRET(default account only)
CLI setup (
--url/--tokenare aliases for the explicit fields;nc-talkandncwork as channel aliases):openclaw channels add --channel nextcloud-talk \ --url https://cloud.example.com \ --token "<shared-secret>"Equivalent explicit fields:
openclaw channels add --channel nextcloud-talk \ --base-url https://cloud.example.com \ --secret "<shared-secret>"File-backed secret:
openclaw channels add --channel nextcloud-talk \ --base-url https://cloud.example.com \ --secret-file /path/to/nextcloud-talk-secret - Config:
-
Check
openclaw channels status --probe; start the Gateway if it is offline. Config changes follow hot reload. If you changed the service environment, restart the Gateway to load it.
Minimal config:
{
channels: {
"nextcloud-talk": {
enabled: true,
baseUrl: "https://cloud.example.com",
botSecret: "shared-secret",
dmPolicy: "pairing",
},
},
}
Notes
- Bots cannot initiate DMs. The user must message the bot first.
- The Nextcloud server must be able to reach the webhook URL. The Gateway serves
webhookPathon its own HTTP port. Existing installations may retain an explicitlegacyWebhookpin for the former port8788; new installations open no separate port. When using a proxy, setwebhookPublicUrlto the bot's external callback URL. Webhook requests are HMAC-SHA256 signed with the bot secret; invalid signatures are rejected and rate limited. - Message webhooks return HTTP 200 only after the raw event is durably stored; storage failures return HTTP 500. The durable
200carriesx-openclaw-delivery-accepted: durable, so reverse proxies can require the marker to distinguish OpenClaw acceptance from a generic200. Unsupported non-message events return HTTP 200 without the marker and are logged as ignored. - The webhook listener admits at most 64 concurrent unauthenticated body reads; overflow requests receive
HTTP/1.1 429withConnection: close. Requests on one keep-alive connection are answered in order, so a queued delivery's200acknowledgement always flushes before any overflow rejection closes the socket. The 64-read budget is fixed and not configurable. Accounts sharing a Gateway pathname share its admission and failed-authentication budgets; each retained legacy host and port keeps independent budgets. Deployments that regularly saturate it should reduce or buffer upstream concurrency (for example, cap reverse-proxy fan-in toward the listener) and accept that deliveries refused during saturation may be lost. - The bot API sends text and reactions. Native outbound attachments use the configured API account instead (see Outbound attachments).
- The webhook payload does not distinguish DMs from rooms; set
apiUser+apiPasswordto enable room-type lookups (cached about 5 minutes). Without them, every conversation is treated as a room. Inbound attachment retrieval also requires this API account. - Network fetches, including authenticated inbound attachment requests and outbound requests, go through the SSRF guard. For a Nextcloud host on a trusted private/internal network, opt in with
channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork: true. - With
apiUser/apiPasswordandwebhookPublicUrlset,openclaw channels statuschecks the bot and warns when theresponsefeature is missing.
Pre-July-2026 JSON replay caches under <state-dir>/nextcloud-talk/replay-dedupe/
are outside the supported upgrade window. Doctor leaves these files unchanged and
stops with recovery guidance. Upgrade through 2026.9.5
and run its Doctor before installing the latest version. Supported SQLite replay
state continues to migrate into durable webhook deduplication.
Moving existing webhook endpoints to the Gateway
Doctor preserves the previous webhook listener address with a one-shot
legacyWebhook: { port: 8788, host: "0.0.0.0" } pin for enabled accounts on an
existing installation that relied on the implicit endpoint. Evidence of prior
operation is required; fresh installations open no separate port. An explicit
{ port, host? } object selects that endpoint; an omitted host uses 0.0.0.0.
Omitted or false opens no listener, subject to account inheritance. Explicit
listeners use the same Gateway route, HMAC verification, and channel handler.
Nextcloud stores the bot callback URL outside OpenClaw, so an update cannot safely
rewrite it. To use only the Gateway listener, keep the public HTTPS address and
change the reverse proxy's upstream to the Gateway HTTP port (18789 by default),
preserving /nextcloud-talk-webhook or your configured webhookPath. If Nextcloud
connects directly, update the bot callback to a reachable HTTPS endpoint for that
Gateway route instead. Update webhookPublicUrl if its public address changes;
this field records the address for status checks and does not change Nextcloud's
bot registration.
openclaw doctor --fix moves legacy webhookPort and webhookHost settings over to legacyWebhook, following Doctor's usual config backup and write flow. A host-only setting keeps that host along with port 8788. Named accounts retain the endpoint they effectively use. Settings that already exist, an inherited false included, win over retired keys. Both Doctor and startup report the listener and Gateway destination in effect, and neither alters the external callback URL.
The deprecated TypeScript input fields webhookPort and webhookHost stay source-compatible through the next Plugin SDK major. Runtime config relies on legacyWebhook; execute openclaw doctor --fix to move the old keys across.
A Gateway path may be shared by several accounts: the backend origin plus signature has to resolve to exactly one account, so when a Gateway path is shared, give each account on the same Nextcloud backend its own bot secret. Where separate legacy endpoints exist, they keep their original account selection plus their own admission and failed-authentication budgets, even if those accounts share a backend and secret.
Webhook path matching stays exact, query strings, case, and trailing slashes included. Gateway checks cover /health, /healthz, /ready, /readyz, /startup, and /startupz, and that holds when a query string comes after the path too. Anything beneath /api/channels needs Gateway authentication, encoded aliases included; Nextcloud's signature does not provide it. Doctor flags such paths, and an account lacking an explicit legacy listener is unable to start with one. Change webhookPath to /nextcloud-talk-webhook, then point the Nextcloud bot callback and the reverse-proxy upstream at the Gateway port and that path. Throughout this cutover an enabled legacy listener continues to serve its configured path; OpenClaw never rewrites the callback path on its own.
Once delivery through the Gateway route is confirmed, drop the legacyWebhook pin and restart the account so its legacy forwarding is turned off. A shared listener remains open as long as another account is using that endpoint. Preserve meta.migrations.webhookListeners, which Doctor stores alongside the pin, so that later runs will not recreate it. Included and read-only config sources are covered in webhook migrations. To override an inherited root listener, set an explicit false on the account.
Legacy ports return the exact /healthz response: 200 ok carrying Content-Type: text/plain for every ordinary HTTP method (HEAD returns no body). That health path is not matched by query strings, case changes, or trailing slashes. As a result, existing reverse-proxy health checks keep working. When you move the proxy upstream, rely on the Gateway's own health checks and openclaw channels status --probe; the main Gateway port continues to answer with its existing JSON check responses.
Access control (DMs)
- Default:
channels.nextcloud-talk.dmPolicy = "pairing". Senders who are unknown receive a pairing code. - Ways to approve:
openclaw pairing list nextcloud-talkopenclaw pairing approve nextcloud-talk <CODE>
- Public DMs:
channels.nextcloud-talk.dmPolicy="open"together withchannels.nextcloud-talk.allowFrom=["*"]. - Sender allowlists take either bare Nextcloud user IDs (
alice) or typed Talk actor IDs (users/alice), matched case-insensitively; display names are not considered. This holds forallowFrom,groupAllowFrom, roomallowFrom,mediaAllowFrom, and any configured approvers. Actor types likeguests/alicestay distinct fromalice.
Rooms (groups)
- Default:
channels.nextcloud-talk.groupPolicy = "allowlist"(mention-gated). - Allowlist rooms via
channels.nextcloud-talk.rooms, keyed by room token; a wildcard default is set by"*":
{
channels: {
"nextcloud-talk": {
rooms: {
"room-token": { requireMention: true },
},
},
},
}
- Per-room keys:
requireMention(default true),enabled(false disables the room),allowFrom(per-room sender allowlist),tools(allow/deny tool overrides),skills(limit loaded skills),systemPrompt. - To permit no rooms at all, leave the allowlist empty or set
channels.nextcloud-talk.groupPolicy="disabled".
Quoted replies
Text-reply webhooks on Talk 21 and later may carry the text and author of the quoted message. OpenClaw treats that immediate parent as untrusted reply context, subject to the context-visibility policy already in force. The quote leaves the current sender, command text, mention requirements, session, and outgoing reply target untouched. Neither extra API credentials nor a history request is required.
From quoted files, only the caption or filename is contributed; no new download or transcription happens. Older webhooks and malformed optional quotes fall back to normal message handling. Talk's file/voice Activity webhooks can leave out the parent; OpenClaw neither fetches nor infers missing quote context.
Inbound file shares
- To notify the bot, share the file into the Talk conversation. A supported Talk file-share webhook is not emitted if the file is only uploaded to Nextcloud Files, or if a Files share is created without posting it in Talk.
channels.nextcloud-talk.mediaAllowFromis an account-level allowlist of Nextcloud user IDs whose attachments may be downloaded and processed. When omitted or empty at the channel root, it fails closed. Named accounts inherit the root setting when omitted; an explicit empty list denies that account's media. Use["*"]only when every otherwise-authorized sender may send media.- This media gate never expands access. The ordinary DM/group, room, sender, and mention requirements must admit the message first. When only the media gate denies an attachment, the file is not downloaded, but an otherwise-admissible caption continues through the ordinary text path.
channels.nextcloud-talk.mediaMaxMblimits inbound staging and outbound media size. Positive fractional limits are rounded down to whole bytes. When unset, it falls back toagents.defaults.mediaMaxMb(default: 20 MB).- Talk's webhook link must be on the exact configured
baseUrlorigin (same scheme, hostname, and effective port). Because Talk room shares are participant-only, OpenClaw uses the configuredapiUser/API password to reread the exact room message without changing its read, notification, or online state; verify its file metadata against the signed webhook; resolve the account's canonical Nextcloud user ID; and verify the WebDAV file ID before retrieving the file. The API account must be able to access the room share; no sender credentials are requested or used. - Authenticated attachment requests never follow redirects. Talk's
hide-downloadrestriction, the existing SSRF/private-network policy, and the configured byte limit remain enforced before or during staging. - After media authorization, a hidden, malformed, wrong-origin, unavailable, or oversize file is omitted from media context. Its caption continues with a generic attachment-unavailable marker for the agent, while the specific bounded reason is logged for operators.
Outbound attachments
Bot-only accounts keep the existing URL-text delivery for HTTP(S) attachment links; OpenClaw does not fetch or upload those links. This is not a native file share. Local files and non-web locations require the API account below and are never posted as local path text. URLs containing embedded credentials are not eligible for bot-only delivery.
Native attachments require apiUser and apiPassword (or apiPasswordFile) for
that channel account. The bot shared secret alone cannot upload or share files.
Use a dedicated Nextcloud user with an app password, writable Files storage and
sufficient quota, and add it as a participant with permission to chat in every
target room. OpenClaw does not add the user to rooms or create public-link shares.
Partially configured or unavailable API credentials produce an error rather
than silently falling back to URL delivery. Native upload/share failures also
never trigger a second bot send.
Files appear as messages from the API user, not the webhook bot. Text-only replies continue to use the bot. Captions and reply references are included in the native file share when supported by the server; ordinary audio is sent as a file, not relabeled as a voice note.
Explicit voice replies (message with asVoice: true, or the existing TTS reply
flow) use Talk's native voice-note presentation for MP3 and WAV audio. They require
the same API account as native attachments; a bot-only URL link is not a voice
note. Captions and reply references are preserved. Other formats remain ordinary
attachments, even when voice delivery is requested; OpenClaw does not transcode
them or relabel non-audio files as speech. Voice synthesis still uses your existing
TTS configuration and does not enable automatic spoken replies by itself.
OpenClaw loads attachments through its shared outbound media policy, including
approved local-file roots and mediaMaxMb. It uploads a uniquely named file to
OpenClaw/ in the API user's Files, without overwriting existing files, and grants
the target conversation read access. Successful files remain there so recipients
can continue to download them; remove them manually when no longer needed.
If sharing has started but chat delivery cannot be confirmed, OpenClaw reports uncertain delivery and retains the upload. Check the room before retrying: the Nextcloud share API is not idempotent, and retrying can post a duplicate. An unshared upload is cleaned up when preparation fails before sharing. Own file webhooks are recognized without ignoring the API user's other messages; delayed echo recognition depends on retaining the account's bot secret.
Release qualification for attachment support
The release owner must qualify the first host release containing
runtime.channel.media.deleteMediaBuffer before publishing this plugin with
attachment support. This change does not assign that future release number or
claim that an existing registry artifact contains the API.
Use the existing full-release candidate and publication workflow, not a separate plugin-only publication:
- Select the actual new host release version and run the normal version sync.
scripts/sync-plugin-versions.tsraises this official plugin’s version,openclaw.compat.pluginApi, peer requirement, and build version to that release. It intentionally leavesopenclaw.install.minHostVersionunchanged: the release-aligned plugin API floor is what excludes hosts missing this API. Do not publish the attachment implementation with the old API floor. - Qualify the exact host and plugin tarballs bound by the immutable full-release candidate manifest. Require the SDK surface report and its explicit release acknowledgement through the existing release gates. A manifest or a successful source test alone does not establish stock-runtime compatibility.
- Test the exact artifacts together: the new host must load the new plugin and stage, retain accepted, and delete rejected/cancelled attachments through the public runtime API. The previous supported host must reject an explicit new plugin install before activation. Also test a host downgrade with the new plugin already installed: external-plugin discovery must skip the incompatible API floor and preserve its configuration. Check the new host with the previous plugin and the unpinned stable installer’s compatible-version fallback.
- Preserve the existing publication ordering. The combined workflow publishes plugin npm artifacts before core npm, with ClawHub publication started alongside plugins. Those registries are not an atomic transaction. During the gap, the new plugin’s API floor must prevent activation on an old host; an unpinned stable install can select an older compatible plugin instead. Publishing the host first is not a substitute for this compatibility contract.
- Before declaring the release available, read back the exact published host and plugin artifacts, npm dist-tags, and ClawHub version/compatibility metadata. Verify them against the qualified manifest and repeat the stock-artifact compatibility checks. A queued ClawHub publication is not metadata availability.
The package-version sync and install/discovery regression tests exercise the local enforcement path. Registry readback, actual stock-runtime tests, and release SDK acknowledgement remain release-time responsibilities.
Silent sends
Use the message tool's silent: true option to post text or attachments without
creating Talk chat notifications, including mention notifications. Messages remain
visible in the conversation. Omitted or false keeps normal notification behavior;
no channel configuration change is needed.
Silent attachments require the server's media-caption capability (Talk 18+).
OpenClaw checks this before reading or uploading the attachment and reports an
error if support cannot be confirmed, rather than falling back to a notifying send.
Capabilities
| Feature | Status |
|---|---|
| Direct messages | Supported |
| Rooms | Supported |
| Threads | Not supported |
| Media | Inbound and outbound file shares |
| Reactions | Supported |
| Native commands | Not supported |
Configuration reference (Nextcloud Talk)
Full configuration: Configuration
Provider options:
channels.nextcloud-talk.enabled: turn channel startup on or off.channels.nextcloud-talk.baseUrl: URL of the Nextcloud instance.channels.nextcloud-talk.botSecret: the bot's shared secret, given either as a string or as a secret reference.channels.nextcloud-talk.botSecretFile: path to a secret stored in a regular file. Symlinks are rejected.channels.nextcloud-talk.apiUser: API login used for room lookups (DM detection), the status check, and authorized attachments in both directions. OpenClaw maps this login to its canonical Nextcloud user ID when building WebDAV paths.channels.nextcloud-talk.apiPassword: API/app password for authenticated room lookups, fetching inbound attachments, and uploading or sharing outbound files.channels.nextcloud-talk.apiPasswordFile: path to the API password file.channels.nextcloud-talk.legacyWebhook:false | { port, host? }. Supplying an object starts a forwarding listener;hostfalls back to0.0.0.0. If the key is left out or set tofalse, no listener is started. Named accounts take the root setting unless they set their own.channels.nextcloud-talk.webhookPath: webhook path (default: /nextcloud-talk-webhook).channels.nextcloud-talk.webhookPublicUrl: webhook URL that can be reached from outside.channels.nextcloud-talk.dmPolicy:pairing | allowlist | open | disabled(default: pairing). ChoosingopenmakesallowFrom=["*"]mandatory.channels.nextcloud-talk.allowFrom: allowlist of user IDs permitted to send DMs.channels.nextcloud-talk.mediaAllowFrom: allowlist of user IDs permitted to send inbound attachments; if omitted or empty at the root, access is denied by default. Named accounts inherit the root list unless they override it; an explicitly empty list blocks media. Ordinary authorization and mention gating still apply on top of it.channels.nextcloud-talk.groupPolicy:allowlist | open | disabled(default: allowlist).channels.nextcloud-talk.groupAllowFrom: allowlist of user IDs permitted to send in rooms; when unset,allowFromis used instead.channels.nextcloud-talk.rooms: settings and allowlist per room (described above).- Static sender access groups can be referenced from
allowFromandgroupAllowFromwithaccessGroup:<name>. channels.nextcloud-talk.historyLimit: history limit for groups (0 turns it off).channels.nextcloud-talk.dmHistoryLimit: history limit for DMs (0 turns it off).channels.nextcloud-talk.dms: per-DM overrides keyed by user ID (historyLimit).channels.nextcloud-talk.textChunkLimit: size in characters of outbound text chunks (default: 4000).channels.nextcloud-talk.mediaMaxMb: media size cap in MB for inbound and outbound; when unset,agents.defaults.mediaMaxMbapplies (default: 20).channels.nextcloud-talk.streaming.chunkMode:length(default) ornewlineto split at blank lines (paragraph boundaries) before chunking by length.channels.nextcloud-talk.streaming.block.enabled: turns block streaming on or off for this channel.channels.nextcloud-talk.streaming.block.coalesce: coalesce tuning for block streaming.channels.nextcloud-talk.replyToMode: reply-reference mode (off | first | all | batched; default:all). A named account may override it throughchannels.nextcloud-talk.accounts.<id>.replyToMode.channels.nextcloud-talk.responsePrefix: prefix added to outbound replies.channels.nextcloud-talk.markdown.tables: how markdown tables are rendered (off | bullets | code | block).channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork: lets private/internal Nextcloud hosts through the SSRF guard.channels.nextcloud-talk.accounts.<id>: overrides per account (same keys);defaultAccountselects the default. The env varsNEXTCLOUD_TALK_BOT_SECRET/NEXTCLOUD_TALK_API_PASSWORDaffect the default account only.
Related
- Channels Overview, all supported channels
- Pairing, DM authentication and pairing flow
- Groups, group chat behavior and mention gating
- Channel routing, session routing for messages
- Reactions, emoji reaction semantics for the
messagetool - Security, access model and hardening