ClickClack Bot Token Channel Setup for OpenClaw

Set up a self-hosted ClickClack workspace as an OpenClaw channel using native bot tokens. Covers creating standalone or user-owned bots, claiming setup codes, and target syntax.

Read this when

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

ClickClack links OpenClaw to a ClickClack workspace you host yourself, using native ClickClack bot tokens.

Reach for this when an OpenClaw agent needs to show up as a bot user inside ClickClack. Two bot types exist there: standalone service bots and user-owned bots. A user-owned bot retains an owner_user_id and gets only the token scopes you hand it.

Quick setup

Inside ClickClack, go to Workspace settings → Integrations → OpenClaw, then create a bot with Setup code (recommended) and copy the command that gets generated:

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

When the frontend and API live on different origins, or the API is mounted under a path, ClickClack instead issues an exact claim endpoint:

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

A setup code works once and expires 10 minutes after creation. OpenClaw claims it, gets back the freshly minted bot token plus workspace settings, stores the account, checks the connection, and tells you whether the gateway already running has picked it up. With versioned exact endpoints, OpenClaw checks and saves whatever API base ClickClack returns, path prefix included. The setup code itself never lands in OpenClaw config.

For public servers, setup-code claims go over HTTPS. Local installs on loopback addresses like localhost and 127.0.0.1 may also use plain HTTP.

Should OpenClaw already be running, ClickClack hooks up on its own and you need no second command. If it is not running, launch it with:

openclaw gateway

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

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

For a guided walkthrough, run:

openclaw onboard

Pick ClickClack, then supply the server URL, bot token, and workspace as prompted. After saving, guided setup verifies the server, token, and workspace; if that check fails, your configuration is kept.

Alternative: manual token

Pick Manual token in ClickClack if you are setting up a client other than OpenClaw, or if you want to manage the token by hand:

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

workspace takes a workspace id (wsp_...), a slug, or a display name. --code cannot be combined with --token, --token-file, or --use-env.

Alternative: env-based token

Rather than keeping 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 use a configured token or token file; the shared env variable is deliberately restricted to the default account.

Configuration

JSON5 reference

The same thing expressed as config:

{
  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 treated as configured only if 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 takes a workspace id (wsp_...), slug, or name, and the gateway resolves it to the id during startup.

Account config keys

KeyDefaultNotes
baseUrlnone (required)Public ClickClack URL used for browser-facing links.
apiBaseUrlbaseUrlOptional server-to-server endpoint for REST and realtime WebSocket traffic.
tokennoneBot token as a plain string or secret ref (source: "env" | "file" | "exec" | "store").
tokenFilenonePath to a bot-token file; takes precedence over token.
workspacenone (required)Workspace id, slug, or name.
replyMode"agent""agent" runs the full agent pipeline; "model" sends short direct model completions.
defaultTo"channel:general"Target used when an outbound path gives no target.
allowFrom["*"]User-id allowlist for inbound DMs and channel messages.
allowBotsfalseAdmit messages authored by other ClickClack bots: true for all allowed bot messages or "mentions" in groups only.
botLoopProtectionbuilt-in defaultsSliding-window bot-pair loop guard applied to admitted bot messages.
botUserIdauto-detectedResolved from the bot token identity at startup.
agentIdroute defaultPin this account's inbound messages to one agent.
toolsAllownoneTool allowlist for agent replies from this account.
model, systemPromptnoneUsed by replyMode: "model" completions.
commandMenutruePublish native commands to ClickClack composer autocomplete.
reconnectMs1500Realtime reconnect delay (100 to 60000).
discussionsdisabledManaged per-session channel settings; see Session discussions.
requireMentionfalseRequire a direct mention before dispatching group messages. See Group mention gating.
requireMentionInBotThreadsunsetOverride mention gating in threads started by this account's bot. See Bot-created threads.
mentionPatterns[]Mention patterns for this account in group channels. See Group mention gating.
groups{}Per-channel group policy overrides keyed by ClickClack channel ID. See Group mention gating.

Keep an auth-gated public hostname

Reach for apiBaseUrl when the OpenClaw gateway and ClickClack share a host, yet an authentication gateway such as Cloudflare Access guards the public ClickClack hostname:

{
  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 still find the public hostname fully auth-gated. For REST requests, setup verification, and the realtime WebSocket, OpenClaw goes through the loopback endpoint instead, while discussion embedUrl and openUrl links keep pointing at the public baseUrl. Should apiBaseUrl be left out, all traffic falls back to baseUrl and existing behavior stays as it was.

Plugin allowlist behavior

When plugins.allow holds a non-empty restrictive list, either picking ClickClack explicitly during channel setup or running openclaw plugins enable clickclack adds clickclack to that list. Onboarding installation follows the same explicit-selection rule. Neither path overrides plugins.deny or a global plugins.enabled: false setting. A direct openclaw plugins install @openclaw/clickclack obeys the usual plugin-install policy and additionally records ClickClack in any allowlist already present.

Multiple bots

Every account establishes its own ClickClack realtime connection, authenticated with that account's 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

Turning on discussions for a single ClickClack account gives every OpenClaw session a ClickClack channel of its own. The account token has to carry channels:write (it comes bundled in bot:admin); the ordinary bot:write setup token is unable 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 takes the same workspace id, slug, or display name as the account-level workspace, and falls back to that value when unset. section selects the ClickClack sidebar section and falls back to Sessions. With controlUrlBase set, the managed channel links back to the Control UI session path.

Turn on discussions for exactly one ClickClack account. The gateway provider offers no account selector, so when several discussion accounts are enabled they are rejected outright rather than picked according to configuration order.

A discussion, once opened, becomes a public ClickClack channel flagged as externally managed. The plugin keeps the session label and category synchronized, yet channel lifecycle stays independent. Clearing 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 owns channel archive and restore independently. Bindings are reconciled by the plugin whenever discussion RPCs are used, and roughly once a minute for as long as any bindings exist.

Messages arriving in a managed channel are handled by a side session with a stable key under the same agent id as the attached main session. That side agent is informed which main session it should observe, and it may use sessions_history and session_status (changesSince helps with incremental checks). It calls sessions_send only when discussion participants ask it to relay to or steer the main session. Durable room identity is kept separate from the replaceable session attachment by the binding. The side-session peer identity and scoped grant embed the exact concrete OpenClaw session id, so resetting a reusable session key rotates the attachment and the old side transcript cannot be reused. The ClickClack channel id, URL, history, and ownership reference stay as they were. Messages that arrive through an inactive, disabled, or retargeted attachment are dropped rather than falling back to the account's normal channel routing. A released binding leaves a durable revoked-channel marker behind, so delayed realtime events are still rejected. Remote ownership is keyed by ClickClack server and channel id, which means renaming the local account cannot turn a managed channel into an ordinary one.

For tighter session access, set tools.sessions.visibility explicitly to tree instead of leaving the default all. A host-scoped grant is installed by the plugin only between each side session and its attached main session, together with a tool-policy hook that blocks session discovery and cross-session targets. sessions_history, session_status, and sessions_send are allowed only for the attached main session, and the status call is prevented from changing that session's model. Those tools must still appear in the agent's effective tool allowlist. The system prompt is guidance; authorization is enforced by the host grant and hook.

Managed-channel fields (external_managed, external_ref, external_url, and sidebar_section) must be supported by the ClickClack server on channel creation and updates, and returned in channel responses. OpenClaw checks that contract before persisting a binding. Should a create response be lost, the next open adopts the channel via its server-enforced external_ref rather than creating another one. Until that outcome is reconciled, otherwise-unbound events in the destination workspace are quarantined by the pending reservation. The coarse reconciler adopts the channel while the logical session is active, including after its concrete session id changes; if no remote channel was created, it clears the reservation. That reference holds a durable per-OpenClaw-installation namespace plus a hash of the session key, ClickClack destination, and durable binding generation. Channels of one gateway cannot be adopted by another, while concrete session resets keep the same channel. A round trip through an account or workspace cannot re-adopt a previous channel. Bindings are pinned to the configured ClickClack server URL as well, and become invalid 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. The old room is never archived. If the token was replaced with a workspace-scoped credential that cannot reach the old workspace, OpenClaw marks the old channel as revoked and releases the binding without attempting the replacement token.

A pull-only discussion tool is also given to the attached main session. It returns the latest messages and recent thread replies, one escaped, attributed record per message, and causes no write or lifecycle side effects. Channel-root and thread lookups have fixed request budgets; when that safety bound could omit an older active thread, the result says so explicitly.

Reply modes

  • replyMode: "agent" (default) sends inbound messages through the normal agent pipeline, session recording and tool policy included.
  • replyMode: "model" bypasses the agent pipeline and calls the plugin runtime's llm.complete to reply directly as the bot, optionally shaped by model and systemPrompt. The completion budget belongs to the selected provider and model.

Both modes honor responsePrefix at the channel or account level. Account values take precedence, 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, which includes omitting unresolved model-dependent prefixes.

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

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

Leave the trust bit off if you only use the default agent reply mode; that mode does not need it.

Command menu

When the gateway starts, every configured account publishes OpenClaw's native commands to ClickClack. They show up in composer autocomplete labeled with the bot's handle. On each startup the published set is replaced wholesale, which also clears a stale menu when the native command catalog is empty.

Command-menu sync is on by default. To opt out, set commandMenu: false on an account:

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

The token requires commands:write. Current ClickClack bot:write and bot:admin bundles already include that scope, and it can also be granted on its own. Tokens created before command menus existed may need the scope added or a replacement token.

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

Only native command specs are published in this release. Aliases and skill-, plugin-, or custom-command catalogs are not added to the menu. If a name is registered as an HTTP slash command as well, ClickClack dispatches that registration first; the remaining menu commands continue through normal message delivery.

Use agent mode for cross-service correlation evidence. Given an authoritative ClickClack message id in the required msg_<ulid> format, the channel derives the matching OpenClaw run id clickclack:<message-id>. Every model call then shows in diagnostics as clickclack:<message-id>:model:<n>; when that turn goes through ClawRouter, the same model-call id is sent as X-Request-ID. model mode bypasses the normal agent run/session diagnostics, so it is not suitable for this evidence path.

If a realtime event carries a validated payload.correlation_id, that channel passes it along as X-Correlation-ID on the authoritative message fetch and on the ClickClack reply requests that follow. Only ClickClack's safe 128-character set is accepted (A-Z, a-z, 0-9, ., _, :, and -); anything outside it gets dropped. Identifiers are all these joins hold: no message bodies, prompts, completions, credentials, or tool output ever appear in them.

Durable media delivery

Media-bearing agent replies rely on required durable delivery. Before the first ClickClack write happens, OpenClaw fixes stable per-part message and upload nonces, so a retry reuses that same upload and message rather than burning storage quota or posting duplicates. When an upload already exists after a restart, OpenClaw never re-reads the original local path or remote media URL.

For this recovery contract to hold, the ClickClack server must support:

  • GET /api/uploads/by-nonce with X-ClickClack-Upload-Nonce: supported on both found and missing results.
  • GET /api/messages/by-nonce with X-ClickClack-Message-Nonce: supported on both found and missing results.
  • Message creation and attachment association that stay duplicate-free when retried with the same owner-scoped nonce and upload.

A generic 404 from an older server does not prove a send is absent. Rather than risk a duplicate, OpenClaw leaves the delivery unresolved; upgrade ClickClack before you turn on media-producing agent replies.

Native progress and agent activity rows

Native progress is enabled per account, and only by opt-in. Set nativeProgress: true to display a transient <agent name> is responding status plus progress lines while an agent turn is running. The agent name is drawn from the configured account name, the ClickClack bot handle, or the agent ID. These rely on ephemeral agent.progress events and disappear once the turn ends; durability applies to the final reply alone. To publish durable agent_commentary and agent_tool message rows during the turn, set agentActivity: true as a separate option:

{
  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 on ClickClack deployments that offer the ephemeral realtime endpoint.
  • Durable activity is separately off by default. Persisting activity rows takes agentActivity: true; on its own it does not turn on native progress.
  • Native progress is best effort. Publication goes through the ephemeral realtime endpoint with a bounded request timeout. Should a progress request fail or stall, it is logged and final text delivery cannot be blocked by it.
  • Durable activity requires the agent_activity:write token scope. That scope is distinct from bot:write and is not inherited by it; create the bot token with --scopes bot:write,agent_activity:write before you enable agentActivity.
  • Best-effort degradation. When the token lacks agent_activity:write or the server refuses activity writes, the failures are logged and the final reply still goes out normally; no activity rows will appear.
  • Rows are grouped per turn (turn_id) and coalesced so that one logical step maps to one row, and tool rows reuse the progress formatting used by Discord/Slack/Telegram (tool name plus command detail).
  • Attribution metadata. Posts written by the agent (activity rows and the final reply) include author_model and author_thinking fields, resolved from the model actually used for that turn (fallback included). Servers that do not define these columns simply ignore the unknown JSON fields; servers that persist them can answer, per message, "which model said this line, at which thinking level."

Group mention gating

By default, each group message in ClickClack is dispatched to every enabled ClickClack account within the same workspace, which keeps the behavior backward compatible. Require a direct mention before the agent pipeline runs by adding requireMention: true to an account.

Resolution of each group policy field follows this order:

  1. An exact channel entry in groups (keyed by ClickClack channel ID).
  2. A wildcard "*" entry in groups.
  3. The account-level value, which falls back to the top-level ClickClack value when omitted.
  4. The backward-compatible default ({ requireMention: false, mentionPatterns: [] }).

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

Bot-created threads

With requireMentionInBotThreads: false set, people can reply in threads the bot started without mentioning it, while requireMention: true still applies to other channel messages. This option works at the account level, or inside an exact channel or wildcard groups entry, following the same precedence described above.

OpenClaw applies the override only after verifying that the native thread's root message was authored by the receiving account's bot in the same workspace and channel. Threads someone else started, or whose root cannot be verified, keep the ordinary requireMention behavior. Being a participant in a thread does not make the bot its creator.

When requireMentionInBotThreads: true is set, a mention is required in those threads even if requireMention is false. Leaving the option out preserves the existing mention policy. DMs, sender access rules, and allowBots restrictions stay unchanged; allowBots: "mentions" still requires a mention from another bot.

Mention detection

A ClickClack mention is detected when:

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

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

Bot-to-bot messages

By default ClickClack ignores messages authored by bots. Opt in by setting allowBots: true on the account. To admit bot messages in group channels only when they mention this bot, set allowBots: "mentions"; direct messages stay eligible without a mention. Bot messages still pass through allowFrom, yet bot authors must be listed explicitly by ID, since the wildcard allowFrom: ["*"] default does not authorize bot-authored messages. That wildcard remains available for human traffic. Messages the bot authors itself are always ignored.

Messages from bots that are accepted still go through the bot-pair loop guard that OpenClaw shares across channels. The guard's window, budget, cooldown, and enabled state are tuned with botLoopProtection on the account or with channels.defaults.botLoopProtection. For allowBots and botLoopProtection at the group level, precedence runs exact channel first, then wildcard, then account level, matching the other group policies. Messages at the top-level channel share one channel budget, whereas replies living in separate ClickClack threads each draw on their own thread-root budget.

Activity rows from ClickClack agent_commentary and agent_tool never cause OpenClaw inbound turns, even in cases where the bot that authored them is explicitly allowed.

Some older ClickClack responses leave out author.kind. Such messages are deliberately kept on the legacy allowFrom path: allowFrom: ["*"] is able to admit them, and neither the bot-specific allowBots checks nor the bot-pair loop-protection checks run, since the server never classified the author. Restrictions aimed at bots thus depend on a ClickClack server response that carries author classification.

Configuration example

{
  channels: {
    clickclack: {
      enabled: true,
      token: { source: "env", provider: "default", id: "CLICKCLACK_BOT_TOKEN" },
      workspace: "default",
      requireMention: true,
      requireMentionInBotThreads: false,
      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 },
      },
    },
  },
}

When several accounts sit in one workspace, each evaluates the same message on its own, applying the mention policy and bot identity resolved for that account.

Migration warning

A ClickClack channel ID (for example chn_...) is not automatically a Discord channel ID. Per-channel rules can only be configured with the real ClickClack channel identifier. Reusing Discord IDs is acceptable only if the ClickClack server explicitly stores them as external_ref and the adapter ships a documented translation layer.

If you add requireMention: true but leave allowFrom unrestricted, the sender allowlist behavior for group messages will not change silently. The mention gate acts as one more guard layered on the sender policy that already exists.

Targets

  • channel:<name-or-id> delivers to a workspace channel. A bare target defaults to channel:.
  • dm:<user_id> opens a direct conversation with that user, creating it if none exists.
  • thread:<message_id> answers inside the thread rooted at that message.

Outbound targets given explicitly may also be prefixed with the clickclack: or cc: provider.

For outbound media, ClickClack's upload API is called first, and the durable upload is then attached to the channel message, thread reply, or DM that was created. Local files and supported remote media URLs are handled under OpenClaw's standard media-access policy. Each outbound attachment is capped in MiB by channels.clickclack.mediaMaxMb; the root is overridden by accounts.<id>.mediaMaxMb, and agents.defaults.mediaMaxMb provides the fallback. A 64 MiB ceiling on uploads always applies. Images may be optimized before they are sent. When a durable send is queued, each upload and message part gets its own owner-scoped nonce, and attachment association is retried with those same objects. The server contract and recovery behavior are described in Durable media delivery.

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 enforces ClickClack token scopes.

  • bot:read: read workspace, channel, message, thread, DM, realtime, and profile data.
  • bot:write: bot:read plus channel messages, thread replies, DMs, uploads, and command-menu publishing.
  • bot:admin: bot:write plus channel creation.
  • commands:write: publish the bot's command menu. Part of the current bot:write and bot:admin bundles, and it can also be granted on its own.
  • agent_activity:write: durable agent activity rows (agent_commentary / agent_tool). Neither bot:write nor bot:admin inherits it; it is needed only when agentActivity: true is set.

For ordinary agent chat and command-menu sync, OpenClaw needs only the current bot:write. Add agent_activity:write when you turn on native progress and agent activity rows.

Troubleshooting

  • ClickClack is not configured for account "<id>": for that account, configure baseUrl, token (through CLICKCLACK_BOT_TOKEN, for instance), and workspace.
  • ClickClack workspace not found: <value>: assign the workspace id, slug, or name that ClickClack returns to workspace.
  • Inbound replies do not arrive: check that the token carries realtime read access. Messages the bot sends itself are always ignored; messages from other bots are denied unless allowed, and if allowBots is turned on, the sender bot ID must additionally appear explicitly in allowFrom.
  • Channel sends are failing: make sure the bot belongs to the workspace and holds bot:write.
  • The command menu does not appear: check that commandMenu is set to something other than false, that the ClickClack server has support for PUT /api/bots/self/commands, and that the token includes commands:write.
4,164 words · updated Oct 25, 2026