Nextcloud Talk is a downloadable channel plugin (@openclaw/nextcloud-talk) that connects Apus to a self-hosted Nextcloud instance through a Talk webhook bot. Direct messages, rooms, reactions, and markdown messages are supported; media goes out as URLs.

Install

apus plugins install @openclaw/nextcloud-talk

Use the bare package spec to follow the current official release tag. Pin an exact version only when you need a reproducible install.

From a local checkout (dev workflows):

apus plugins install ./path/to/local/nextcloud-talk-plugin

Check the application result after installing.

How messages reach the agent

sequenceDiagram
    participant User as Talk user
    participant Talk as Nextcloud Talk
    participant Hook as Apus webhook
    participant Agent as Apus 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

The webhook acknowledgement confirms durable receipt, not permission to run an agent or completion of its reply. Access checks still apply after acceptance. Unknown DM senders may receive a pairing code instead of an agent reply. The webhook URL must be reachable from Nextcloud; replies travel back through the Talk API rather than in the webhook response.

Quick setup (beginner)

  1. Install the plugin (above).

  2. Publish the Gateway webhook route /nextcloud-talk-webhook through your HTTPS reverse proxy, forwarding to the Gateway port (default 18789). On your Nextcloud server, create a bot using that public URL:

    ./occ talk:bot:install "Apus" "<shared-secret>" "<webhook-url>" --feature webhook --feature response --feature reaction
    

    Keep --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.

  3. Enable the bot in the target room settings.

  4. Configure Apus:

    • Config: channels.nextcloud-talk.baseUrl + channels.nextcloud-talk.botSecret
    • Or env: NEXTCLOUD_TALK_BOT_SECRET (default account only)

    CLI setup (--url/--token are aliases for the explicit fields; nc-talk and nc work as channel aliases):

    apus channels add --channel nextcloud-talk \
      --url https://cloud.example.com \
      --token "<shared-secret>"
    

    Equivalent explicit fields:

    apus channels add --channel nextcloud-talk \
      --base-url https://cloud.example.com \
      --secret "<shared-secret>"
    

    File-backed secret:

    apus channels add --channel nextcloud-talk \
      --base-url https://cloud.example.com \
      --secret-file /path/to/nextcloud-talk-secret
    
  5. Check apus 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

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 Apus, 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.

apus doctor --fix migrates old webhookPort and webhookHost settings to legacyWebhook, using Doctor's normal config backup and write flow. Host-only settings preserve that host with port 8788. Named accounts preserve their effective endpoint. Existing canonical settings, including an inherited false, take precedence over retired keys. Doctor and startup report the effective listener and Gateway destination without changing the external callback URL.

The deprecated TypeScript webhookPort and webhookHost input fields remain source-compatible until the next Plugin SDK major. Runtime config uses legacyWebhook; run apus doctor --fix to migrate the old keys.

Accounts can share a Gateway path: backend origin and signature must identify exactly one account, so give accounts on the same Nextcloud backend distinct bot secrets when sharing a Gateway path. Separate legacy endpoints preserve their original account selection and independent admission and failed-authentication budgets even when the accounts have the same backend and secret.

Webhook paths retain exact request matching, including query strings, case, and trailing slashes. /health, /healthz, /ready, /readyz, /startup, and /startupz belong to Gateway checks, including when a query string follows. Paths under /api/channels require Gateway authentication, including encoded aliases; Nextcloud's signature does not supply that authentication. Doctor warns about these paths, and an account without an explicit legacy listener cannot start with one. Set webhookPath to /nextcloud-talk-webhook and update the Nextcloud bot callback and reverse-proxy upstream to the Gateway port and that path. An enabled legacy listener keeps serving its configured path during this cutover; Apus does not silently rewrite the callback path.

After verifying delivery through the Gateway route, remove the legacyWebhook pin and restart the account to disable its legacy forwarding. A shared listener stays open while another account uses that endpoint. Keep meta.migrations.webhookListeners, which Doctor saves with the pin, so later runs do not recreate it. See webhook migrations for included and read-only config sources. Use explicit false on an account to override an inherited root listener. Legacy ports preserve the exact /healthz response: 200 ok with Content-Type: text/plain for every ordinary HTTP method (HEAD has no body). Query strings, case changes, and trailing slashes do not match that health path. This keeps existing reverse-proxy health checks working. When moving the proxy upstream, use the Gateway's own health checks and apus channels status --probe; the main Gateway port keeps its existing JSON check responses.

Access control (DMs)

Rooms (groups)

{
  channels: {
    "nextcloud-talk": {
      rooms: {
        "room-token": { requireMention: true },
      },
    },
  },
}

Capabilities

Feature Status
Direct messages Supported
Rooms Supported
Threads Not supported
Media URL-only
Reactions Supported
Native commands Not supported

Configuration reference (Nextcloud Talk)

Full configuration: Configuration

Provider options:

Related