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_integrationscollection 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:
- First-time setup — register the Slack App at
api.slack.com/appsand wire the env vars (one-time, Boris). - Per-customer install — a dashboard admin clicks “Add to Slack” from
/settings/integrations.
Step 1: Register the Slack App (one-time, ops only)
- Open api.slack.com/apps and click Create New App > From scratch.
- App name:
Curate-Me(production) orCurate-Me Dev(staging). - Workspace: Pick the Curate-Me internal Slack workspace.
- After creation, copy the Client ID and Client Secret from Basic Information > App Credentials.
- 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:
| Scope | Why we need it |
|---|---|
app_mentions:read | Receive @curate-me mentions in channels |
chat:write | Post governance alerts and HITL approval cards |
chat:write.public | Post into public channels the bot has not joined |
channels:manage | Provision the command-center channel during onboarding |
channels:read | List public channels for the channel picker |
commands | Register /cm status, /cm cost, etc. |
files:write | Upload acceptance-harness artifacts |
groups:read | List private channels (for channel picker if invited) |
users:read | Resolve Slack users for DM HITL delivery |
users:read.email | Resolve Slack users to org members on first message |
im:read / im:write / im:history | DM HITL escalation path |
reactions:write | Acknowledge 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:
| Command | Request URL | Short description |
|---|---|---|
/cm | https://api.curate-me.ai/api/v1/gateway/slack/commands | Curate-Me governance commands |
(The single /cm command parses subcommands like status, cost, approve, etc. server-side.)
Step 5: Enable Event Subscriptions
In Event Subscriptions:
- Enable Events.
- Request URL:
https://api.curate-me.ai/api/v1/admin/integrations/slack/webhook - 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 backendVerification: 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:
- Org admin opens Dashboard > Settings > Integrations.
- Clicks Configure on the Slack card → modal opens.
- Clicks Add to Slack → a new tab opens to Slack OAuth.
- Customer picks their Slack workspace and authorizes.
- Slack redirects to
/api/v1/admin/integrations/slack/callback→ backend stores encrypted token, fetches channels. - Original tab polls
/api/v1/admin/integrations/slackevery 3 seconds and updates whenstatus === 'connected'. - 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.
Related code
- 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:
- Disable for one org: Customer clicks Disconnect in dashboard or call
DELETE /api/v1/admin/integrations/slackwith their JWT. - Disable globally (kill switch): Unset
SLACK_CLIENT_IDin.env.productionand restart the backend./installwill start returning HTTP 503 immediately. Existing connected workspaces keep working until their token is revoked. - 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.