Skip to Content
RunbooksRunbook: Slack Integration Setup

Runbook: Slack Integration Setup

Owner: Platform Team Backup owner: On-call engineer Last validated: 2026-05-18 Validation method: Manual install against the dev Slack workspace Severity trigger: SEV3 (notifications inoperable, no data loss) Customer impact: Approval/error/budget alerts silently dropped until reconnected Required access: Slack Admin in the target workspace, VPS SSH (for env vars) Related services: curateme-backend-gateway (port 8001), MongoDB slack_integrations collection Time to complete: ~15 minutes (first-time app registration); ~2 minutes (per-customer install)


The Curate-Me Slack integration runs as a single multi-workspace Slack App that customers install via OAuth from the dashboard. Each install stores the bot token (encrypted) in MongoDB so we can send notifications, deliver HITL approvals, and process slash commands per org.

This runbook covers two scenarios:

  1. First-time setup — register the Slack App at api.slack.com/apps and wire the env vars (one-time, Boris).
  2. Per-customer install — a dashboard admin clicks “Add to Slack” from /settings/integrations.

Step 1: Register the Slack App (one-time, ops only)

  1. Open api.slack.com/apps  and click Create New App > From scratch.
  2. App name: Curate-Me (production) or Curate-Me Dev (staging).
  3. Workspace: Pick the Curate-Me internal Slack workspace.
  4. After creation, copy the Client ID and Client Secret from Basic Information > App Credentials.
  5. Copy the Signing Secret from the same panel (used to verify incoming webhook signatures).

Step 2: Configure OAuth scopes

In your new app’s OAuth & Permissions page, add these Bot Token Scopes:

ScopeWhy we need it
app_mentions:readReceive @curate-me mentions in channels
chat:writePost governance alerts and HITL approval cards
chat:write.publicPost into public channels the bot has not joined
channels:manageProvision the command-center channel during onboarding
channels:readList public channels for the channel picker
commandsRegister /cm status, /cm cost, etc.
files:writeUpload acceptance-harness artifacts
groups:readList private channels (for channel picker if invited)
users:readResolve Slack users for DM HITL delivery
users:read.emailResolve Slack users to org members on first message
im:read / im:write / im:historyDM HITL escalation path
reactions:writeAcknowledge approvals with reactions

These scopes are the canonical list — they are also enforced by SlackOAuthService.BOT_SCOPES in services/backend/src/services/integrations/slack_oauth.py. Any change here MUST be mirrored in code so OAuth re-installs request the right set.


Step 3: Configure redirect URIs

Still in OAuth & Permissions, add Redirect URLs:

  • Production: https://api.curate-me.ai/api/v1/admin/integrations/slack/callback
  • Staging: https://staging-api.curate-me.ai/api/v1/admin/integrations/slack/callback
  • Local dev: http://localhost:8001/api/v1/admin/integrations/slack/callback

The callback path is hard-coded in SlackOAuthService.CALLBACK_PATH. If you change it in code, update the Slack App registration too.


Step 4: Enable Slash Commands

In Slash Commands, click Create New Command for each:

CommandRequest URLShort description
/cmhttps://api.curate-me.ai/api/v1/gateway/slack/commandsCurate-Me governance commands

(The single /cm command parses subcommands like status, cost, approve, etc. server-side.)


Step 5: Enable Event Subscriptions

In Event Subscriptions:

  1. Enable Events.
  2. Request URL: https://api.curate-me.ai/api/v1/admin/integrations/slack/webhook
  3. Subscribe to bot events: app_mention, message.im.

Slack will send a url_verification challenge on save — the backend handles this transparently in admin_integrations_slack.handle_slack_webhook.


Step 6: Set environment variables

Add these to .env.production (VPS) and .env.example is the source of truth for local dev:

SLACK_CLIENT_ID= # From Step 1 (Basic Information) SLACK_CLIENT_SECRET= # From Step 1 (Basic Information) SLACK_SIGNING_SECRET= # From Step 1 (Basic Information) # Optional: override the auto-derived redirect URI. # If unset, it's computed from B2B_API_URL + /api/v1/admin/integrations/slack/callback. SLACK_REDIRECT_URI=

Apply on VPS:

ssh curateme@178.105.8.25 cd /home/curateme/curate-me/platform vim .env.production # paste in the three values docker compose -f docker-compose.production.yml restart backend

Verification: Hit the install endpoint with a valid admin JWT:

curl -X POST https://api.curate-me.ai/api/v1/admin/integrations/slack/install \ -H "Authorization: Bearer $JWT" \ -H "X-Org-ID: $ORG_ID" # Expected: { "authorization_url": "https://slack.com/oauth/v2/authorize?...", "state": "..." } # If you get HTTP 503 with "Slack OAuth is not configured", env vars are missing.

Step 7: Per-customer install (customer-facing)

After Steps 1-6 are done in production, customers can self-serve:

  1. Org admin opens Dashboard > Settings > Integrations.
  2. Clicks Configure on the Slack card → modal opens.
  3. Clicks Add to Slack → a new tab opens to Slack OAuth.
  4. Customer picks their Slack workspace and authorizes.
  5. Slack redirects to /api/v1/admin/integrations/slack/callback → backend stores encrypted token, fetches channels.
  6. Original tab polls /api/v1/admin/integrations/slack every 3 seconds and updates when status === 'connected'.
  7. Customer picks a default channel and clicks Send Test Message to verify.

Troubleshooting

”Slack OAuth is not configured” (HTTP 503 on /install)

Missing one of SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, or a derivable SLACK_REDIRECT_URI. Check .env.production and restart the backend.

OAuth callback returns “invalid_state”

The CSRF state token expired (10 min TTL) or Redis lost it. Have the customer retry — the dashboard will mint a fresh state.

”channels:manage scope is missing” on command-center provisioning

The Slack App was installed before we added the channels:manage scope. Customer needs to Reinstall App from Settings > Integrations > Slack > Disconnect then Add to Slack again.

Test message returns “no_default_channel”

Customer authorized the install but never picked a default channel. Walk them to Default Channel in the modal.

Webhook receives but doesn’t process

Check X-Slack-Signature validation in services/backend/src/services/integrations/slack_webhook.py. If signing secret rotated, the secret in .env.production no longer matches the one Slack signs with — re-copy from Basic Information.


  • Backend: services/backend/src/api/routes/admin_integrations_slack.py
  • OAuth service: services/backend/src/services/integrations/slack_oauth.py
  • Slack API client: services/backend/src/services/integrations/slack_service.py
  • Block Kit builder: services/backend/src/services/integrations/slack_blocks.py
  • Dashboard panel: apps/dashboard/components/settings/integrations/slack-setup-panel/
  • Dashboard full page: apps/dashboard/app/settings/integrations/slack/page.tsx

Rollback

If the integration causes problems org-wide:

  1. Disable for one org: Customer clicks Disconnect in dashboard or call DELETE /api/v1/admin/integrations/slack with their JWT.
  2. Disable globally (kill switch): Unset SLACK_CLIENT_ID in .env.production and restart the backend. /install will start returning HTTP 503 immediately. Existing connected workspaces keep working until their token is revoked.
  3. Revoke a stale token manually: Use the Slack admin UI on api.slack.com/apps → your app → Manage Distribution > Workspace approvals to revoke per-workspace.