Integrations
ClickClack ships two integration surfaces designed to look like a small subset of Mattermost so existing scripts can post messages without rewriting.
#App installations
An app installation binds a named integration to a workspace and to the bot user that will act for it. It is workspace integration metadata, not a runtime plugin loader.
POST /api/workspaces/{workspace_id}/app-installations
Content-Type: application/json
{
"app_slug": "openclaw",
"display_name": "OpenClaw",
"bot_user_id": "usr_...",
"config": {
"default_channel_id": "chn_..."
}
}
Behavior:
- Any human workspace member can list installations.
- Creating or revoking requires a human workspace owner or moderator. Bot
bot_user_idmust be a bot member of the same workspace.app_slugidentifies the app manifest, not a singleton. A workspace may- Guided setup sends a private
setup_noncewith bot-token and installation GET /api/workspaces/{workspace_id}/app-installationslists activePOST /api/app-installations/{installation_id}/revokeatomically revokes the- Its optional body fields are
revoke_slash_commands(defaulttrue), delete_botis also optional and defaults tofalse. When enabled, uninstall- Bot tokens for the installation's bot remain active unless
- The revoke response contains the installation plus exact counts for revoked
tokens cannot mutate app installations.
create any number of active installations for the same slug; each installation has its own ID, bot binding, token lifecycle, and config.
creation. Retrying after a lost response reuses the same bot, token row, and installation instead of creating duplicate active resources. The token row receives a fresh raw token on retry, so the previous unconfirmed secret no longer authenticates and no raw token is stored for replay.
installations.
binding and, by default, its attached slash commands and event subscriptions.
revoke_event_subscriptions (default true), and revoke_bot_tokens (default false).
globally retires the bound bot, revokes all of that bot's resources across workspaces, preserves historical attribution, and releases its handle. User-owned bots require their owner; service bots require manager access in every affected workspace.
revoke_bot_tokens is explicitly true, because the bot may serve other integrations.
slash commands, event subscriptions, and bot tokens. A successful delete_bot response also contains deleted_bot. Repeating an ordinary revoke returns zero counts.
The TypeScript SDK exposes this as client.apps.list(workspaceId), client.apps.install(workspaceId, input), and client.apps.revoke(id, options).
#Incoming webhook
POST /api/hooks/mattermost/{channel_id}
Content-Type: application/json
{ "text": "deploy ✅" }
Behavior:
- Authenticates the caller like any other API request. Use bearer session
- Posts the
textfield as a Markdown channel message authored by the - Emits a
message.createddurable event.
tokens for real integrations; reserve X-ClickClack-User for local/dev setups.
current user.
Mattermost's full incoming webhook schema (username, icon_emoji, attachments, etc.) is not honored — only text. Field names match so existing senders don't crash.
#Registered slash commands
Use registered slash commands when an app owns the command and needs a signed callback:
POST /api/workspaces/{workspace_id}/slash-commands
Content-Type: application/json
{
"app_installation_id": "app_...",
"command": "/deploy",
"description": "Deploy an environment",
"callback_url": "https://example.com/clickclack/deploy",
"bot_user_id": "usr_..."
}
Behavior:
- Any human workspace member can list slash commands.
- Creating, revoking, or rotating a secret requires a human workspace owner or
bot_user_idmust be a bot member of the same workspace.commandis normalized to a lowercase leading-slash command such asPOST /api/workspaces/{workspace_id}/slash-commandsreturns a one-timePOST /api/slash-commands/{command_id}/rotate-secretupdates the- Invoking the command through
/api/hooks/slash/{channel_id}sends JSON to - Invocation requires the caller's current write authority for the exact
- Registered invocation attempts consume the guest write budget once persisted,
- Callback delivery connects directly to public IP addresses only. It rejects
- If the callback returns
{"response_type":"in_channel","text":"..."}, the
moderator. Bot tokens cannot mutate slash command registrations.
/deploy.
signing_secret; list calls redact it.
registration in place and returns the fresh one-time signing_secret. Rotating a revoked command returns 400.
callback_url with X-ClickClack-Timestamp and X-ClickClack-Signature: sha256=<hex hmac>, where the signed string is <timestamp>.<raw-json-body>.
channel, including guest-channel, timeout, block, and guest-budget checks.
including invocations whose callback later fails.
private, loopback, link-local, reserved, or mixed public/private DNS answers, does not use environment-configured proxies, and does not follow redirects.
server posts that text as the command's bot user. response_type defaults to in_channel.
The callback payload includes command_id, command, text, workspace_id, channel_id, user_id, bot_user_id, and trigger_id. Each invocation is stored with callback status/body/error metadata for later audit.
The TypeScript SDK exposes this as client.slashCommands.list(workspaceId), client.slashCommands.create(workspaceId, input), client.slashCommands.revoke(id), and client.slashCommands.rotateSecret(id).
In the web app, channel composers discover registered commands inline: type / at the start of a draft to open the slash-command menu, keep typing to filter by command name, then use the arrow keys plus Enter or Tab to insert the selected command. The menu overlays the timeline instead of resizing the message list.
Bot-declared command menus are a separate backend metadata source for the web composer integration. When that integration merges both systems, an HTTP-registered slash command wins at dispatch time if the same name exists in both. Bot-declared and otherwise unknown commands fall through to normal plain-message delivery. The two systems intentionally have no cross-system uniqueness constraint.
The same composer menu supports @ mentions for workspace people and bots. Mention suggestions work in channel, DM, and thread composers; selection uses the same mouse, arrow-key, Enter, and Tab controls as slash commands.
#Outgoing event subscriptions
Outgoing event subscriptions push durable event-log entries to app callbacks. They are intentionally built on the same event records used by realtime WebSocket replay.
POST /api/workspaces/{workspace_id}/event-subscriptions
Content-Type: application/json
{
"app_installation_id": "app_...",
"event_types": ["message.created", "reaction.added"],
"callback_url": "https://example.com/clickclack/events"
}
Behavior:
- Any human workspace member can list subscriptions and delivery attempts.
- Creating, revoking, or rotating a secret requires a human workspace owner or
event_typesaccepts exact durable event types or*; unknown event typesGET /api/event-typesreturns the accepted durable event vocabulary to any- Creation returns a one-time
signing_secret; list calls redact it. POST /api/event-subscriptions/{subscription_id}/rotate-secretupdates the- Delivery posts
{"subscription_id":"sub_...","event": Event}as JSON to the - Delivery uses the same signature headers as slash commands, plus
- Delivery uses the same public-address, no-proxy, no-redirect policy as slash
- Every delivery attempt is stored with response status, response body, error,
GET /api/event-subscriptions/{subscription_id}/deliveriesacceptslimit
moderator. Bot tokens cannot mutate subscriptions.
return 400.
authenticated user.
subscription in place and returns the fresh one-time signing_secret. Existing delivery history remains attached. Rotating a revoked subscription returns 400.
callback URL.
X-ClickClack-Event-ID.
command callbacks.
and attempt number.
(default 50, maximum 200) and a before delivery-attempt ID. Results are newest first and return {"deliveries":[...],"next_cursor":"eda_..."}. next_cursor is null on the last page. If retention removes the delivery referenced by before, the API returns 400 instead of silently treating the stale cursor as the final page.
The TypeScript SDK exposes this as client.eventSubscriptions.list(workspaceId), client.eventSubscriptions.create(workspaceId, input), client.eventSubscriptions.revoke(id), and client.eventSubscriptions.rotateSecret(id). Use client.eventSubscriptions.deliveries(id, { limit, before }) for delivery pages and client.eventTypes.list() for the durable event vocabulary.
#Connected accounts and audit log
Connected accounts record external identities or app-side account bindings for a workspace user:
POST /api/workspaces/{workspace_id}/connected-accounts
Content-Type: application/json
{
"user_id": "usr_...",
"provider": "github",
"provider_account_id": "123456",
"display_name": "octocat",
"scopes": ["repo:read"],
"metadata": {"login": "octocat"}
}
Behavior:
- Any human workspace member can list connected accounts.
- Creating a connected account requires a human workspace owner or moderator.
- The target
user_idmust be a member of the workspace. GET /api/workspaces/{workspace_id}/connected-accountslists activePOST /api/connected-accounts/{account_id}/revokerevokes the account- Create and revoke write audit entries.
connected accounts.
binding. A workspace owner or moderator can revoke any binding; the bound user can revoke their own.
Audit entries are available from GET /api/workspaces/{workspace_id}/audit-log. The SDK exposes these as client.connectedAccounts.* and client.auditLog.list(workspaceId).
#Compatibility slash command callback
POST /api/hooks/slash/{channel_id}
Content-Type: application/x-www-form-urlencoded
command=/deploy&text=staging
Behavior:
- Posts
<command> <text>(trimmed) as a channel message authored by the - Returns a Mattermost-shaped response:
caller.
{
"response_type": "in_channel",
"text": "<command> <text>",
"message": Message,
"event": Event
}
If the posted command is not registered, ClickClack falls back to the legacy Mattermost-shaped behavior above and posts <command> <text> directly as the caller. This keeps simple scripts working while registered commands handle real app callbacks.
#What is intentionally missing
- Retry scheduling for failed outgoing deliveries.
- Mattermost client compatibility (REST, WebSocket protocol).