| read_when |
|
|---|
ClickClack ships two integration surfaces designed to look like a small subset of Mattermost so existing scripts can post messages without rewriting.
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 tokens cannot mutate app installations.
bot_user_idmust be a bot member of the same workspace.app_slugidentifies the app manifest, not a singleton. A workspace may create any number of active installations for the same slug; each installation has its own ID, bot binding, token lifecycle, and config.- Guided setup sends a private
setup_noncewith bot-token and installation 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. GET /api/workspaces/{workspace_id}/app-installationslists active installations.POST /api/app-installations/{installation_id}/revokeatomically revokes the binding and, by default, its attached slash commands and event subscriptions.- Its optional body fields are
revoke_slash_commands(defaulttrue),revoke_event_subscriptions(defaulttrue), andrevoke_bot_tokens(defaultfalse). delete_botis also optional and defaults tofalse. When enabled, uninstall 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.- Bot tokens for the installation's bot remain active unless
revoke_bot_tokensis explicitlytrue, because the bot may serve other integrations. - The revoke response contains the installation plus exact counts for revoked
slash commands, event subscriptions, and bot tokens. A successful
delete_botresponse also containsdeleted_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).
POST /api/hooks/mattermost/{channel_id}
Content-Type: application/json
{ "text": "deploy ✅" }Behavior:
- Authenticates the caller like any other API request. Use bearer session
tokens for real integrations; reserve
X-ClickClack-Userfor local/dev setups. - Posts the
textfield as a Markdown channel message authored by the current user. - Emits a
message.createddurable event.
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.
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 moderator. Bot tokens cannot mutate slash command registrations.
bot_user_idmust be a bot member of the same workspace.commandis normalized to a lowercase leading-slash command such as/deploy.POST /api/workspaces/{workspace_id}/slash-commandsreturns a one-timesigning_secret; list calls redact it.POST /api/slash-commands/{command_id}/rotate-secretupdates the registration in place and returns the fresh one-timesigning_secret. Rotating a revoked command returns400.- Invoking the command through
/api/hooks/slash/{channel_id}sends JSON tocallback_urlwithX-ClickClack-TimestampandX-ClickClack-Signature: sha256=<hex hmac>, where the signed string is<timestamp>.<raw-json-body>. - If the callback returns
{"response_type":"in_channel","text":"..."}, the server posts that text as the command's bot user.response_typedefaults toin_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 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 moderator. Bot tokens cannot mutate subscriptions.
event_typesaccepts exact durable event types or*; unknown event types return400.GET /api/event-typesreturns the accepted durable event vocabulary to any authenticated user.- Creation returns a one-time
signing_secret; list calls redact it. POST /api/event-subscriptions/{subscription_id}/rotate-secretupdates the subscription in place and returns the fresh one-timesigning_secret. Existing delivery history remains attached. Rotating a revoked subscription returns400.- Delivery posts
{"subscription_id":"sub_...","event": Event}as JSON to the callback URL. - Delivery uses the same signature headers as slash commands, plus
X-ClickClack-Event-ID. - Every delivery attempt is stored with response status, response body, error, and attempt number.
GET /api/event-subscriptions/{subscription_id}/deliveriesacceptslimit(default50, maximum200) and abeforedelivery-attempt ID. Results are newest first and return{"deliveries":[...],"next_cursor":"eda_..."}.next_cursorisnullon the last page. If retention removes the delivery referenced bybefore, the API returns400instead 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 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 active connected accounts.POST /api/connected-accounts/{account_id}/revokerevokes the account binding. A workspace owner or moderator can revoke any binding; the bound user can revoke their own.- Create and revoke write audit entries.
Audit entries are available from GET /api/workspaces/{workspace_id}/audit-log.
The SDK exposes these as client.connectedAccounts.* and
client.auditLog.list(workspaceId).
POST /api/hooks/slash/{channel_id}
Content-Type: application/x-www-form-urlencoded
command=/deploy&text=stagingBehavior:
- Posts
<command> <text>(trimmed) as a channel message authored by the caller. - Returns a Mattermost-shaped response:
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.
- Retry scheduling for failed outgoing deliveries.
- Mattermost client compatibility (REST, WebSocket protocol).
{ "response_type": "in_channel", "text": "<command> <text>", "message": Message, "event": Event }