ClickClack Bot-Token Setup and Target Syntax

Learn how to connect OpenClaw to a self-hosted ClickClack workspace using native bot tokens. Covers quick setup via setup code, token scopes, and target syntax for both standalone and user-owned bots.

Read this when

  • Connecting OpenClaw to a ClickClack workspace
  • Testing ClickClack bot identities

ClickClack links OpenClaw with a self-hosted ClickClack workspace, relying on native ClickClack bot tokens for authentication.

This is the right choice when you need an OpenClaw agent to show up as a bot user inside ClickClack. Both standalone service bots and user-owned bots are supported; the latter retain an owner_user_id and only get the token scopes you assign.

Quick setup

Head to Workspace settings → Integrations → OpenClaw within ClickClack, generate a bot via Setup code (recommended), then copy the command that appears:

openclaw channels add clickclack --code 'https://clickclack.example.com/#XXXX-XXXX-XXXX'

When the frontend and API live on separate origins, or the API sits behind a path prefix, ClickClack provides an exact claim endpoint instead:

openclaw channels add clickclack --code 'https://api.example.com/services/clickclack/api/bot-setup-codes/claim#XXXX-XXXX-XXXX'

That setup code works for a single use and lapses after ten minutes. OpenClaw claims it, obtains the freshly issued bot token along with workspace settings, stores the account, checks the connection, and tells you whether the active gateway accepted it. For versioned exact endpoints, OpenClaw validates and keeps the canonical API base that ClickClack returns, path prefix included. The setup code never ends up in OpenClaw's config.

Claiming via setup code runs over HTTPS on public servers. Plain HTTP remains available for local setups bound to loopback addresses like localhost and 127.0.0.1.

If OpenClaw is up and running, ClickClack hooks in automatically, so no extra command is required. Otherwise, launch it with:

openclaw gateway

The code can also be supplied separately from the server URL:

openclaw channels add clickclack --code XXXX-XXXX-XXXX --base-url https://clickclack.example.com

For an interactive setup, run:

openclaw onboard

Pick ClickClack, then provide the server URL, bot token, and workspace when asked. Guided setup verifies the server, token, and workspace after saving; a failed check leaves the configuration intact.

Alternative: manual token

Opt for Manual token inside ClickClack when setting up a client other than OpenClaw, or when you prefer to handle the token yourself:

openclaw channels add clickclack --base-url https://clickclack.example.com --token ccb_... --workspace default

workspace takes a workspace id (wsp_...), slug, or display name. --code won't work alongside --token, --token-file, or --use-env.

Alternative: env-based token

Rather than putting a token in config, the default account can read CLICKCLACK_BOT_TOKEN:

export CLICKCLACK_BOT_TOKEN="ccb_..."
openclaw channels add clickclack --base-url https://clickclack.example.com --workspace default --use-env
openclaw gateway

Named accounts have to rely on a configured token or token file; the shared environment variable is deliberately restricted to the default account.

JSON5 reference

Here is the matching config structure:

{
  channels: {
    clickclack: {
      enabled: true,
      baseUrl: "https://clickclack.example.com",
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      defaultTo: "channel:general",
    },
  },
}

An account is considered configured only when baseUrl, a token source, and workspace are all present. For the default account, the token source may be token, tokenFile, or CLICKCLACK_BOT_TOKEN. workspace accepts a workspace id (wsp_...), slug, or name; the gateway maps it to the id on startup.

Account config keys

KeyDefaultNotes
baseUrlnone (required)Browser-facing links point to this public ClickClack URL.
apiBaseUrlbaseUrlOptional server-to-server endpoint handling REST and realtime WebSocket traffic.
tokennoneBot token supplied as a plain string or secret ref (source: "env" | "file" | "exec" | "store").
tokenFilenoneFile path holding the bot token; overrides token when set.
workspacenone (required)Workspace identified by id, slug, or name.
replyMode"agent""agent" executes the complete agent pipeline; "model" delivers brief direct model completions.
defaultTo"channel:general"Fallback target when an outbound path specifies no target.
allowFrom["*"]Allowlist of user ids permitted for inbound DMs and channel messages.
allowBotsfalseAccept messages from other ClickClack bots: true permits all bot messages, "mentions" restricts to groups.
botLoopProtectionbuilt-in defaultsSliding-window loop guard for bot pairs, applied to admitted bot messages.
botUserIdauto-detectedDetermined from the bot token identity during startup.
agentIdroute defaultPin this account's inbound messages to a single agent.
toolsAllownoneTool allowlist governing agent replies from this account.
model, systemPromptnoneConsumed by replyMode: "model" completions.
commandMenutrueExpose native commands in ClickClack composer autocomplete.
reconnectMs1500Realtime reconnect delay, ranging from 100 to 60000.
discussionsdisabledPer-session channel settings managed individually; refer to Session discussions.
requireMentionfalseGroup messages dispatch only after a direct mention. See Group mention gating.
mentionPatterns[]Mention patterns tied to this account in group channels. See Group mention gating.
groups{}Channel-specific group policy overrides, keyed by ClickClack channel ID. See Group mention gating.

Keep an auth-gated public hostname

When ClickClack and the OpenClaw gateway share a host, and the public ClickClack hostname sits behind an authentication gateway like Cloudflare Access, apply apiBaseUrl:

{
  channels: {
    clickclack: {
      baseUrl: "https://clack.openclaw.ai",
      apiBaseUrl: "http://127.0.0.1:8484",
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
    },
  },
}

Browser users can keep the public hostname fully auth-gated. OpenClaw relies on the loopback endpoint for REST calls, setup verification, and the realtime WebSocket, while discussion embedUrl and openUrl links still point to the public baseUrl. Omitting apiBaseUrl routes all traffic through baseUrl, which matches prior behavior.

When plugins.allow holds a non-empty restrictive list, choosing ClickClack explicitly during channel setup or executing openclaw plugins enable clickclack adds clickclack to that list. Onboarding installation follows the same explicit-selection path. Neither approach overrides plugins.deny or a global plugins.enabled: false setting. Direct openclaw plugins install @openclaw/clickclack adheres to the standard plugin-install policy and also logs ClickClack into an existing allowlist.

Multiple bots

Each account maintains its own ClickClack realtime connection and uses a distinct bot token.

{
  channels: {
    clickclack: {
      enabled: true,
      baseUrl: "https://clickclack.example.com",
      defaultAccount: "service",
      accounts: {
        service: {
          token: { source: "env", provider: "default", id: "CLICKCLACK_SERVICE_BOT_TOKEN" },
          workspace: "default",
          defaultTo: "channel:general",
          agentId: "service-bot",
        },
        support: {
          token: { source: "env", provider: "default", id: "CLICKCLACK_SUPPORT_BOT_TOKEN" },
          workspace: "default",
          defaultTo: "dm:usr_...",
          agentId: "support-bot",
        },
      },
    },
  },
}

Session discussions

Enabling discussions on a single ClickClack account grants each OpenClaw session its own dedicated ClickClack channel. The account token must contain channels:write (the bot:admin bundle includes this scope); the standard bot:write setup token lacks the ability to create or synchronize channels.

{
  channels: {
    clickclack: {
      enabled: true,
      baseUrl: "https://clickclack.example.com",
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      discussions: {
        enabled: true,
        workspace: "default",
        controlUrlBase: "https://team.openclaw.ai",
        section: "Sessions",
      },
    },
  },
}

discussions.workspace accepts the same workspace id, slug, or display name as the account-level workspace and defaults to that value. section determines the ClickClack sidebar section, defaulting to Sessions. When controlUrlBase is configured, the managed channel links back to the canonical Control UI session path.

Only one ClickClack account may have discussions enabled. Because the gateway provider lacks an account selector, enabling multiple discussion accounts results in rejection rather than selection by configuration order.

Opening a discussion creates a public ClickClack channel flagged as externally managed. The plugin keeps the session label and category synchronized, while channel lifecycle remains separate. Removing the session category returns the channel to the configured default section. Archiving, resetting, or deleting an OpenClaw session never archives or replaces the ClickClack channel. ClickClack independently owns channel archive and restore operations. The plugin reconciles bindings when discussion RPCs are invoked and roughly once per minute while any bindings exist.

Inbound messages in a managed channel use a deterministic side session under the same agent id as the attached main session. The side agent is informed which main session to observe and can employ sessions_history and session_status (changesSince helps with incremental checks). It uses sessions_send only when discussion participants request relay or steering of the main session. The binding separates durable room identity from its replaceable session attachment. The side-session peer identity and scoped grant include the exact concrete OpenClaw session id, so resetting a reusable session key rotates the attachment and cannot reuse the old side transcript. The ClickClack channel id, URL, history, and ownership reference remain unchanged. Messages arriving through an inactive, disabled, or retargeted attachment are dropped instead of falling back to the account's normal channel routing. Released bindings leave a durable revoked-channel marker so delayed realtime events remain fail-closed. Remote ownership is keyed by ClickClack server and channel id, so renaming the local account cannot convert a managed channel into an ordinary one.

Maintain tools.sessions.visibility at its safer default tree. The plugin installs a host-scoped grant only between each side session and its attached main session, plus a tool-policy hook that blocks session discovery and cross-session targets. It permits sessions_history, session_status, and sessions_send only for the attached main session and prevents the status call from altering that session's model. Those tools must still appear in the agent's effective tool allowlist. The system prompt serves as guidance; the host grant and hook form the authorization boundary.

The ClickClack server must support managed-channel fields (external_managed, external_ref, external_url, and sidebar_section) on channel creation and updates and return them in channel responses. OpenClaw verifies that contract before persisting a binding. If a create response is lost, the next open adopts the channel by its server-enforced external_ref instead of creating another. Until that outcome is reconciled, the pending reservation quarantines otherwise-unbound events in the destination workspace. The coarse reconciler adopts the channel when the logical session is active, including after its concrete session id changes; it clears the reservation when no remote channel was created.

That reference contains a durable per-OpenClaw-installation namespace plus a hash of the session key, ClickClack destination, and durable binding generation. Separate gateways cannot adopt each other's channels, while concrete session resets keep the same channel. An account or workspace round trip cannot re-adopt a previous channel. Bindings are also pinned to the configured ClickClack server URL and are invalidated if the account is retargeted. Changing or removing controlUrlBase updates or clears the managed channel link on the next reconciliation pass. Changing discussions.workspace releases the old attachment before a channel can be opened in the new workspace. It never archives the old room. If the token was replaced with a workspace-scoped credential that cannot access the old workspace, OpenClaw records the old channel as revoked and releases the binding without trying the replacement token.

The attached main session also receives a pull-only discussion tool. It reads the latest messages and recent thread replies as one escaped, attributed record per message, and has no write or lifecycle side effects. Channel-root and thread lookups have fixed request budgets; the result explicitly warns when that safety bound can omit an older active thread.

Reply modes

  • replyMode: "agent" (default) dispatches inbound messages through the normal agent pipeline, including session recording and tool policy.
  • replyMode: "model" skips the agent pipeline and uses the plugin runtime's llm.complete for direct bot replies, optionally shaped by model and systemPrompt. The selected provider and model own the completion budget.

Both modes honor responsePrefix at the channel or account level. Account values win, including "" to disable an inherited prefix. Use "auto" for the routed agent's identity name or "[{model}]" for the selected model. Explicit message tool and CLI text sends follow the shared prefix behavior, including omission of unresolved model-dependent prefixes.

Model mode runs completions against the resolved bot agent id, which requires the explicit plugins.entries.clickclack.llm.allowAgentIdOverride: true trust bit:

{
  plugins: {
    entries: {
      clickclack: {
        llm: {
          allowAgentIdOverride: true,
        },
      },
    },
  },
}

Keep the trust bit off if you only use the default agent reply mode; it is not needed there.

Command menu

At gateway startup, each configured account publishes OpenClaw's native commands to ClickClack. They appear in composer autocomplete labeled with the bot's handle. The published set is replaced wholesale on each startup, including clearing a stale menu when the native command catalog is empty.

Command-menu sync is enabled by default. Set commandMenu: false on an account to opt out:

{
  channels: {
    clickclack: {
      enabled: true,
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      commandMenu: false,
    },
  },
}

The token needs commands:write. Current ClickClack bot:write and bot:admin bundles include that scope, and it can also be granted individually. Tokens created before command menus were introduced may need the scope added or a replacement token.

Sync is best effort and runs once per gateway start. A missing scope or network failure logs a warning; an older ClickClack server without the endpoint logs at debug level. None of these failures block realtime startup. Menus remain available while the agent is offline and are removed when the bot leaves the workspace.

This release publishes native command specs only. Aliases and skill-, plugin-, or custom-command catalogs are not added to the menu. If a name is also registered as an HTTP slash command, ClickClack dispatches that registration first; other menu commands continue through normal message delivery.

Use agent mode for cross-service correlation evidence. For an authoritative ClickClack message id in its canonical msg_<ulid> shape, the channel derives the deterministic OpenClaw run id clickclack:<message-id>. Each model call is then visible in diagnostics as clickclack:<message-id>:model:<n>; when that turn uses ClawRouter, the same model-call id is sent as X-Request-ID. model mode bypasses the normal agent run/session diagnostics and is therefore not suitable for this evidence path.

When a realtime event includes a validated payload.correlation_id, the channel exposes it as X-Correlation-ID on the authoritative message fetch and on the ClickClack reply requests that follow. The values rely on ClickClack's safe 128-character set (A-Z, a-z, 0-9, ., _, :, and -); anything invalid gets dropped. These joins hold only identifiers, never message bodies, prompts, completions, credentials, or tool output.

Durable media delivery

Media-bearing agent replies depend on required durable delivery. Before the first ClickClack write, OpenClaw assigns stable per-part message and upload nonces, so a retry reuses the same upload and message rather than burning storage quota or emitting duplicates. If an upload already exists after a restart, OpenClaw does not reread the original local path or remote media URL.

This recovery contract calls for a ClickClack server that provides:

  • GET /api/uploads/by-nonce with X-ClickClack-Upload-Nonce: supported on found and missing results.
  • GET /api/messages/by-nonce with X-ClickClack-Message-Nonce: supported on found and missing results.
  • Idempotent message creation and attachment association for the same owner-scoped nonce and upload.

A generic 404 from an older server is not accepted as proof that a send is missing. OpenClaw leaves the delivery unresolved instead of risking a duplicate; upgrade ClickClack before enabling media-producing agent replies.

Native progress and agent activity rows

Native progress is opt-in per account. Configure nativeProgress: true to display a transient <agent name> is responding status and progress lines while an agent turn runs. The agent name derives from the configured account name, ClickClack bot handle, or agent ID. These rely on ephemeral agent.progress events and are removed when the turn ends; only the final reply is durable. Set agentActivity: true separately to publish durable agent_commentary and agent_tool message rows while the turn is in progress:

{
  channels: {
    clickclack: {
      enabled: true,
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      nativeProgress: true,
      agentActivity: true,
    },
  },
}

Requirements and behavior:

  • Native progress is off by default. Enable nativeProgress: true only for ClickClack deployments that support the ephemeral realtime endpoint.
  • Durable activity is separately off by default. Enable agentActivity: true to persist activity rows; this does not turn on native progress by itself.
  • Native progress is best effort. Progress publication uses the ephemeral realtime endpoint and a bounded request timeout. A failed or stalled progress request is logged and cannot block final text delivery.
  • Durable activity requires the agent_activity:write token scope. This scope is separate from bot:write and is not inherited by it; create the bot token with --scopes bot:write,agent_activity:write before enabling agentActivity.
  • Best-effort degradation. If the token lacks agent_activity:write or the server rejects activity writes, failures are logged and the final reply still delivers normally; no activity rows appear.
  • Rows are grouped per turn (turn_id), coalesced so one logical step is one row, and tool rows use the same progress formatting as Discord/Slack/Telegram (tool name plus command detail).
  • Attribution metadata. Agent-authored posts (activity rows and the final reply) carry author_model and author_thinking fields resolved from the actual model used for the turn (including after fallback). Servers that do not define these columns ignore the unknown JSON fields; servers that persist them can answer "which model said this line, at which thinking level" per message.

Group mention gating

By default, every group message in ClickClack dispatches to every enabled ClickClack account in the same workspace. This behavior is backward compatible. Add requireMention: true to an account to require a direct mention before the agent pipeline runs.

The effective policy is resolved in this order:

  1. Exact channel entry in groups (keyed by ClickClack channel ID).
  2. Wildcard "*" entry in groups.
  3. Account-level requireMention / mentionPatterns.
  4. Backward-compatible default ({ requireMention: false, mentionPatterns: [] }).

DMs are never gated by requireMention. When a DM arrives, the mention gate is skipped entirely.

Mention detection

ClickClack mentions are detected when:

  • The message body matches any pattern in mentionPatterns (each pattern is a regular expression).
  • The message contains the bot's ClickClack @handle. The gateway reads the handle from the authenticated bot identity at startup.

Plain display names (e.g. Blackbird) are not treated as mentions unless they are explicitly configured as a pattern.

Bot-to-bot messages

ClickClack ignores bot-authored messages by default. To opt in, set allowBots: true on the account. Set allowBots: "mentions" to admit bot messages in group channels only when they mention this bot; direct messages remain eligible without a mention. Bot messages still pass through allowFrom, but bot authors must be explicitly listed by ID; the wildcard allowFrom: ["*"] default does not authorize bot-authored messages. The wildcard remains available for human traffic. Self-authored messages are always ignored.

Accepted bot messages also pass through OpenClaw's shared bot-pair loop guard. Use botLoopProtection on the account or channels.defaults.botLoopProtection to tune its window, budget, cooldown, or enabled state. Group-level allowBots and botLoopProtection values follow the same exact-channel, wildcard, then account-level precedence as the other group policies. Top-level channel messages share a channel budget, while replies in different ClickClack threads use independent thread-root budgets.

ClickClack agent_commentary and agent_tool activity rows never trigger OpenClaw inbound turns, even when their author bot is explicitly allowed.

Older ClickClack responses may omit author.kind. Those messages intentionally remain on the legacy allowFrom path: allowFrom: ["*"] can admit them, and the bot-specific allowBots and bot-pair loop-protection checks do not apply because the server did not classify the author. Bot-specific restrictions therefore require a ClickClack server response that includes author classification.

Configuration example

{
  channels: {
    clickclack: {
      enabled: true,
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      requireMention: true,
      mentionPatterns: ["\\bBlackbird\\b"],
      allowBots: "mentions",
      allowFrom: ["usr_trusted_bot"],
      botLoopProtection: { maxEventsPerWindow: 12, windowSeconds: 60 },
      groups: {
        "*": { requireMention: true, allowBots: "mentions" },
        chn_command_and_control: { requireMention: false },
      },
    },
  },
}

Within a single workspace, each account evaluates the same message on its own. An account using requireMention: true will reject a message that was not mentioned, while one with requireMention: false might still process it.

Migration warning

ClickClack channel identifiers, such as chn_..., are not automatically the same as Discord channel IDs. To set up rules for a specific channel, you need the actual ClickClack identifier for that channel. Avoid reusing Discord IDs unless the ClickClack server explicitly keeps them as external_ref and the adapter provides a documented mapping mechanism.

Adding requireMention: true without also limiting allowFrom will not quietly alter the current sender allowlist behavior for group messages; the mention gate acts as an extra check layered on top of the existing sender policy.

Targets

  • channel:<name-or-id> delivers to a workspace channel. When no target is specified, the default is channel:.
  • dm:<user_id> opens a direct conversation with that user, or reuses one that already exists.
  • thread:<message_id> posts a reply within the thread anchored at that message.

Explicit outbound targets can also include the clickclack: or cc: provider prefix.

For outbound media, ClickClack's upload API is used first, then the durable upload gets attached to the resulting channel message, thread reply, or DM. Local files and supported remote media URLs follow OpenClaw's standard media-access policy. To cap each outbound attachment in MiB, set channels.clickclack.mediaMaxMb; accounts.<id>.mediaMaxMb overrides the root, and agents.defaults.mediaMaxMb provides the fallback. The 64 MiB upload ceiling is always in effect. Images may undergo optimization before being sent. Durable queued sends rely on separate owner-scoped nonces for each upload and message part, then retry attaching those same objects. Refer to Durable media delivery for the server contract and recovery details.

Examples:

openclaw message send --channel clickclack --target channel:general --message "hello"
openclaw message send --channel clickclack --target dm:usr_123 --message "hello"
openclaw message send --channel clickclack --target thread:msg_123 --message "following up"

Permissions

The ClickClack API is what enforces token scopes.

  • bot:read: access to workspace, channel, message, thread, DM, realtime, and profile data.
  • bot:write: everything in bot:read, plus channel messages, thread replies, DMs, uploads, and command-menu publishing.
  • bot:admin: everything in bot:write, plus channel creation.
  • commands:write: publishes the bot's command menu. This is part of the current bot:write and bot:admin bundles and can also be granted on its own.
  • agent_activity:write: durable agent activity rows (agent_commentary / agent_tool). Not inherited through bot:write or bot:admin; only needed when agentActivity: true is configured.

For regular agent chat and command-menu sync, OpenClaw only requires the current bot:write. When you enable native progress and agent activity rows, add agent_activity:write as well.

Troubleshooting

  • ClickClack is not configured for account "<id>": for that account, configure baseUrl, token (for instance through CLICKCLACK_BOT_TOKEN), and workspace.
  • ClickClack workspace not found: <value>: assign workspace the workspace id, slug, or name that ClickClack returns.
  • No inbound replies: make sure the token has realtime read access. The bot always ignores its own messages; other bot messages are blocked by default, and when allowBots is turned on, the sender bot ID must also be explicitly added to allowFrom.
  • Channel sends fail: confirm the bot is part of the workspace and holds bot:write.
  • No command menu: check that commandMenu is not false, that the ClickClack server supports PUT /api/bots/self/commands, and that the token includes commands:write.
3,804 words · updated Sep 1, 2026