Skip to content

Slack

Carbon has two Slack integration modes:

  1. Legacy webhook URL (round 1) — paste an Incoming Webhook URL into Settings → Integrations. One channel per URL, no OAuth. Still works.
  2. Real Slack app (round 13) — install Carbon into a Slack workspace via OAuth, then subscribe any channel to any subset of Carbon events. This page covers the real app.
  1. Go to api.slack.com/appsCreate New AppFrom scratch. Name it “Carbon” (or whatever you like).

  2. OAuth & Permissions → add these bot token scopes:

    • channels:read
    • chat:write
    • incoming-webhook
  3. OAuth & PermissionsRedirect URLs → add https://api.your-carbon-host/v1/slack/oauth-callback (use your API’s public origin; http://localhost:4000/v1/slack/oauth-callback for local dev).

  4. Basic Information → copy the Client ID and Client Secret.

  5. Set the following env vars on the Carbon API and workers processes:

    Terminal window
    SLACK_CLIENT_ID=...
    SLACK_CLIENT_SECRET=...
    # 32+ char random string — encrypts stored bot tokens at rest.
    SLACK_TOKEN_ENC_KEY=$(openssl rand -hex 32)
    # Only needed if the callback URL differs from the default.
    SLACK_REDIRECT_URI=https://api.your-carbon-host/v1/slack/oauth-callback
    DASHBOARD_URL=https://app.your-carbon-host
  6. Run the 0011_slack_integrations migration:

    Terminal window
    pnpm --filter @carbon/database migrate
  1. In the Carbon dashboard, go to Settings → Integrations → Slack.
  2. Click Connect Slack. You’ll be sent to Slack’s approval screen and bounced back to /settings?slack=installed.
  3. The workspace now appears under Slack installations.

For each installation you can add per-channel subscriptions. Each subscription selects the event actions that should be forwarded — e.g. snapshot.overwritten, drift.detected, emulator.crashed.

Terminal window
curl -X POST https://api.your-carbon-host/v1/slack/subscriptions \
-H "x-carbon-key: ck_live_admin_…" \
-H "content-type: application/json" \
-d '{
"installationId": "slkinst_...",
"channelId": "C0123456789",
"channelName": "eng-alerts",
"events": ["snapshot.overwritten", "drift.detected", "emulator.crashed"]
}'

Deliveries are handled by the startSlackNotifier worker in apps/workers/src/slack-notifier.ts. It polls the events table every 10s and sends a Block Kit message via chat.postMessage for every matching subscription.

Terminal window
curl -X DELETE https://api.your-carbon-host/v1/slack/installations/slkinst_... \
-H "x-carbon-key: ck_live_admin_…"

The server calls Slack’s auth.revoke (best-effort) and hard-deletes the installation + all its channel subscriptions.

MethodPathPurpose
GET/v1/slack/install302 → Slack OAuth authorize (public)
GET/v1/slack/oauth-callbackOAuth callback → dashboard (public)
GET/v1/slack/installationsList installations for the caller’s org
GET/v1/slack/subscriptionsList channel subscriptions
POST/v1/slack/subscriptionsCreate a channel subscription
DELETE/v1/slack/subscriptions/:idRemove a channel subscription
DELETE/v1/slack/installations/:idUninstall the workspace integration

All management routes require an admin-scoped API key or a session user with the owner/admin role on the caller’s org.

  • Bot tokens are encrypted at rest with AES-256-GCM. Losing SLACK_TOKEN_ENC_KEY renders every stored token undecryptable — rotate by re-installing each workspace, not by editing rows.
  • The OAuth state parameter carries the org id. A production deployment should additionally check a signed cookie against the state before trusting the redirect — the current implementation trusts the state’s org fragment (fine for admin-authenticated dashboards, not for open public installers).
  • chat.postMessage requires the bot to be in the target channel. Invite @Carbon to the channel before subscribing it, or the send returns not_in_channel and the notifier logs a warning.