From fdd2e9ac34ac3c2f605c46a6e87be60c81e07a74 Mon Sep 17 00:00:00 2001 From: singret <100959986+singret@users.noreply.github.com> Date: Tue, 23 Jun 2026 04:24:35 +0000 Subject: [PATCH] docs(slack): correct setup to HTTP Events API, drop dead Socket Mode The Slack setup docs and the in-app wizard described Socket Mode, but the code uses the HTTP Events API (signed POST to /api/v1/slack/{events, interactions,commands}). socketmode is not imported and SLACK_APP_TOKEN is never read. This mismatch made Slack sync silently fail to debug. - Rewrite docs/getting-started/connecting-slack.md: the three Request URLs, a Local development (ngrok) section, the signing-secret-must-match 403 note, the "tunnel URL changes on restart" gotcha, troubleshooting - SlackSetupModal: replace the isLocal "Socket Mode (no public URLs)" text and dead xapp-token step with tunnel guidance (manifest already uses socket_mode_enabled: false) - Remove dead SLACK_APP_TOKEN from .env.example, environment-variables.md, kubernetes.md, docker-compose{,.dev,.ha}.yml, helm secret/values - Fix stale "Slack Socket Mode" comment in teams_event_handler.go - README: pointer to the corrected guide Verified: npm run build green; every step cross-checked against the manifest/scopes/events/endpoints in code, not the old doc. --- .env.example | 1 - README.md | 2 + .../internal/services/teams_event_handler.go | 2 +- .../helm/fluidify-regen/templates/secret.yaml | 3 - deploy/helm/fluidify-regen/values.yaml | 1 - docker-compose.dev.yml | 1 - docker-compose.ha.yml | 1 - docker-compose.yml | 1 - docs/getting-started/connecting-slack.md | 155 ++++++++++++------ docs/self-hosting/environment-variables.md | 2 - docs/self-hosting/kubernetes.md | 3 +- frontend/src/components/SlackSetupModal.tsx | 20 +-- 12 files changed, 115 insertions(+), 77 deletions(-) diff --git a/.env.example b/.env.example index 4dbe0f4a..554f5afa 100644 --- a/.env.example +++ b/.env.example @@ -23,7 +23,6 @@ CORS_ALLOWED_ORIGINS=http://localhost:8080 # Comma-separated list of allowed or # See: https://github.com/FluidifyAI/Regen/blob/main/slack-app-manifest.yaml SLACK_BOT_TOKEN= # xoxb-... (Bot User OAuth Token) SLACK_SIGNING_SECRET= # From Slack App → Basic Information → Signing Secret -SLACK_APP_TOKEN= # xapp-... (App-Level Token, required for Socket Mode) # ── Microsoft Teams (optional) ───────────────────────────────────────────────── # Required for Teams channel auto-creation and bot commands. diff --git a/README.md b/README.md index 4b6fa11f..b1ddca79 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,8 @@ curl -X POST http://localhost:8080/api/v1/webhooks/prometheus \ An incident is created automatically. If Slack is configured, a dedicated channel appears within seconds. +> **Connecting Slack:** Regen uses Slack's HTTP Events API (signed `POST` to `/api/v1/slack/{events,interactions,commands}`) — not Socket Mode. Local dev needs a public tunnel (ngrok). Full setup, including the three Request URLs and troubleshooting, is in [docs/getting-started/connecting-slack.md](docs/getting-started/connecting-slack.md). + --- ## Security diff --git a/backend/internal/services/teams_event_handler.go b/backend/internal/services/teams_event_handler.go index cdd0510b..aee5b66d 100644 --- a/backend/internal/services/teams_event_handler.go +++ b/backend/internal/services/teams_event_handler.go @@ -217,7 +217,7 @@ func (h *TeamsEventHandler) handleStatus(_ context.Context, activity BotActivity // syncMessageToTimeline saves a non-command Teams message as a timeline entry // for the incident associated with the channel. This gives inbound parity with -// Slack Socket Mode: messages posted in the Teams channel appear in the UI timeline. +// Slack: messages posted in the Teams channel appear in the UI timeline. func (h *TeamsEventHandler) syncMessageToTimeline(_ context.Context, activity BotActivity, text string) { if text == "" { return diff --git a/deploy/helm/fluidify-regen/templates/secret.yaml b/deploy/helm/fluidify-regen/templates/secret.yaml index 27ea75e0..f8cdbd41 100644 --- a/deploy/helm/fluidify-regen/templates/secret.yaml +++ b/deploy/helm/fluidify-regen/templates/secret.yaml @@ -21,9 +21,6 @@ stringData: {{- if .Values.secrets.slackSigningSecret }} SLACK_SIGNING_SECRET: {{ .Values.secrets.slackSigningSecret | quote }} {{- end }} - {{- if .Values.secrets.slackAppToken }} - SLACK_APP_TOKEN: {{ .Values.secrets.slackAppToken | quote }} - {{- end }} {{- if .Values.secrets.openaiAPIKey }} OPENAI_API_KEY: {{ .Values.secrets.openaiAPIKey | quote }} {{- end }} diff --git a/deploy/helm/fluidify-regen/values.yaml b/deploy/helm/fluidify-regen/values.yaml index f0f59e36..2da5a6d3 100644 --- a/deploy/helm/fluidify-regen/values.yaml +++ b/deploy/helm/fluidify-regen/values.yaml @@ -85,7 +85,6 @@ secrets: redisURL: "" slackBotToken: "" slackSigningSecret: "" - slackAppToken: "" openaiAPIKey: "" teamsAppID: "" teamsAppPassword: "" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a3a90f68..e13051e4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -55,7 +55,6 @@ services: - REDIS_URL=redis://redis:6379 - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN:-} - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET:-} - - SLACK_APP_TOKEN=${SLACK_APP_TOKEN:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - TEAMS_APP_ID=${TEAMS_APP_ID:-} - TEAMS_APP_PASSWORD=${TEAMS_APP_PASSWORD:-} diff --git a/docker-compose.ha.yml b/docker-compose.ha.yml index 3d5f3f83..6502d9ea 100644 --- a/docker-compose.ha.yml +++ b/docker-compose.ha.yml @@ -259,7 +259,6 @@ services: - REDIS_SENTINEL_MASTER=mymaster - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN} - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET} - - SLACK_APP_TOKEN=${SLACK_APP_TOKEN} - OPENAI_API_KEY=${OPENAI_API_KEY} - TEAMS_APP_ID=${TEAMS_APP_ID} - TEAMS_APP_PASSWORD=${TEAMS_APP_PASSWORD} diff --git a/docker-compose.yml b/docker-compose.yml index 91da6777..8f04a8bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,6 @@ services: - REDIS_URL=redis://redis:6379 - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN:-} - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET:-} - - SLACK_APP_TOKEN=${SLACK_APP_TOKEN:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - TEAMS_APP_ID=${TEAMS_APP_ID:-} - TEAMS_APP_PASSWORD=${TEAMS_APP_PASSWORD:-} diff --git a/docs/getting-started/connecting-slack.md b/docs/getting-started/connecting-slack.md index baeb9b68..e3a086a8 100644 --- a/docs/getting-started/connecting-slack.md +++ b/docs/getting-started/connecting-slack.md @@ -6,110 +6,157 @@ Fluidify Regen integrates with Slack to automatically create incident channels, - Incident channel auto-created when an alert fires (`#inc-042-redis-memory-high`) - Status updates posted to the channel on every lifecycle change -- Slack commands: `/incident new`, `/incident ack`, `/incident resolve`, `/incident status` -- Interactive buttons: **Make me Lead**, **Add Note** +- Slash commands: `/regen new`, `/regen ack`, `/regen resolve`, `/regen status`, `/regen note`, `/regen list` +- Interactive buttons: **Make me Lead**, **Add Note**, **Overview** +- Slack replies synced back into the incident timeline - AI summaries and handoff digests posted directly to the channel -## Step 1: Create a Slack app +## How it works (read this first) -1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App** -2. Choose **From scratch** -3. Name it `Fluidify Regen` and select your workspace -4. Click **Create App** +Regen uses Slack's **HTTP Events API**. Slack delivers everything inbound — messages, button clicks, slash commands — by sending an HTTPS **POST to your server**, signed with your app's signing secret: -## Step 2: Configure OAuth scopes +| What the user does in Slack | Slack POSTs to | Slack app setting | +|---|---|---| +| Sends a message / @mentions the bot | `…/api/v1/slack/events` | **Event Subscriptions** | +| Clicks a button (Make me Lead, etc.) | `…/api/v1/slack/interactions` | **Interactivity & Shortcuts** | +| Runs a slash command (`/regen …`) | `…/api/v1/slack/commands` | **Slash Commands** | -In the left sidebar, go to **OAuth & Permissions** → **Bot Token Scopes** and add: +Two consequences that trip people up: + +1. **Your server must be reachable from the public internet.** Slack can't POST to `localhost` — for local development you need a tunnel (see [Local development](#local-development-ngrok)). +2. **The signing secret stored in Regen must match your Slack app's signing secret**, or every inbound POST is rejected with **403** (signature verification failure). + +> Regen does **not** use Socket Mode. There is no `SLACK_APP_TOKEN` / `xapp-…` token — if you see that in older notes, ignore it; it does nothing. + +## The fast path: the in-app wizard + +The easiest setup is **Settings → Integrations → Slack → Connect Slack** in the Regen UI. It generates a pre-filled Slack **app manifest** (correct scopes, slash command, and all three Request URLs pointing at your Regen URL) so you don't configure them by hand. Follow the wizard, paste your Bot Token and Signing Secret, and you're done. + +The manual steps below are for when you'd rather configure the Slack app yourself. + +## Manual setup + +### Step 1: Create a Slack app + +1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch** +2. Name it `Fluidify Regen`, select your workspace, **Create App** + +### Step 2: Bot Token Scopes + +**OAuth & Permissions → Bot Token Scopes** — add: | Scope | Purpose | |-------|---------| | `channels:manage` | Create and archive incident channels | | `channels:read` | Read channel info | -| `chat:write` | Post messages to channels | +| `channels:history` | Read channel messages (timeline sync) | +| `channels:write.invites` | Invite responders to incident channels | +| `chat:write` | Post messages | | `chat:write.public` | Post to channels the bot hasn't joined | -| `commands` | Register slash commands | +| `commands` | Register the slash command | +| `app_mentions:read` | Respond to `@Fluidify Regen` mentions | +| `reactions:read` | Read reactions (e.g. ack via emoji) | +| `im:write` | Send DMs (shift handoffs, escalations) | | `users:read` | Resolve user display names | | `users:read.email` | Match Slack users to Regen accounts | -## Step 3: Enable Socket Mode +### Step 3: Set the three Request URLs -Socket Mode allows interactive buttons (Make me Lead, Add Note) to work. Without it, buttons will show an error when clicked. +Replace `https://your-domain.com` with your public Regen URL (or your tunnel URL for local dev). -1. Go to **Socket Mode** in the left sidebar -2. Toggle **Enable Socket Mode** on -3. Click **Generate an app-level token** -4. Name it `regen-socket`, add the scope `connections:write` -5. Click **Generate** — copy the token starting with `xapp-` +**Event Subscriptions** → toggle **Enable Events**: +- **Request URL**: `https://your-domain.com/api/v1/slack/events` + Slack immediately sends a `url_verification` challenge — it must show **Verified ✓**. If it returns 403, your signing secret doesn't match (see Step 6). +- **Subscribe to bot events**: `app_mention`, `message.channels`, `reaction_added` -This is your `SLACK_APP_TOKEN`. +**Interactivity & Shortcuts** → toggle **Interactivity** **On**: +- **Request URL**: `https://your-domain.com/api/v1/slack/interactions` + (No verification challenge — this one works the moment you save. **Buttons do nothing until this is set.**) -## Step 4: Add slash commands - -Go to **Slash Commands** → **Create New Command** and add: +**Slash Commands** → **Create New Command**: | Command | Request URL | Description | |---------|-------------|-------------| -| `/incident` | `https://your-domain.com/api/v1/slack/commands` | Manage incidents from Slack | +| `/regen` | `https://your-domain.com/api/v1/slack/commands` | Manage incidents — new, ack, resolve, status, note, lead, list | -If using Socket Mode (recommended), the Request URL is only needed for the Slack app manifest — actual requests come through the Socket connection. +### Step 4: Install the app -## Step 5: Enable Event Subscriptions +**OAuth & Permissions → Install to Workspace** → authorize. Copy the **Bot User OAuth Token** (`xoxb-…`). -1. Go to **Event Subscriptions** → toggle **Enable Events** -2. Under **Subscribe to bot events** add: - - `app_mention` — lets users @mention the bot - - `message.channels` — syncs Slack replies to the timeline +### Step 5: Signing secret -## Step 6: Install the app to your workspace +**Basic Information → App Credentials → Signing Secret** → copy it. -1. Go to **OAuth & Permissions** → click **Install to Workspace** -2. Authorize the app -3. Copy the **Bot User OAuth Token** starting with `xoxb-` +### Step 6: Configure Regen -This is your `SLACK_BOT_TOKEN`. +Either in the UI — **Settings → Integrations → Slack** (recommended; takes effect immediately, no restart) — or via `.env`: -## Step 7: Get your Signing Secret +```env +SLACK_BOT_TOKEN=xoxb-... +SLACK_SIGNING_SECRET=... +``` -Go to **Basic Information** → scroll to **App Credentials** → copy **Signing Secret**. +> The signing secret here **must be identical** to the one on the Slack app's Basic Information page. A mismatch is the #1 cause of inbound 403s. -This is your `SLACK_SIGNING_SECRET`. +There is no `SLACK_APP_TOKEN` — Regen uses the HTTP Events API, not Socket Mode. -## Step 8: Configure Regen +## Local development (ngrok) -Add to your `.env`: +Slack can't reach `localhost`, so you need a public tunnel to your backend (port `8080`): -```env -SLACK_BOT_TOKEN=xoxb-... -SLACK_SIGNING_SECRET=... -SLACK_APP_TOKEN=xapp-... +```bash +ngrok http 8080 ``` -Then restart: `make stop && make start` +ngrok prints a public URL, e.g. `https://abc123.ngrok-free.app`. Use it as the base for **all three** Request URLs in Step 3: -Or configure directly in the UI: **Settings → Integrations → Slack**. +``` +https://abc123.ngrok-free.app/api/v1/slack/events +https://abc123.ngrok-free.app/api/v1/slack/interactions +https://abc123.ngrok-free.app/api/v1/slack/commands +``` -## Verifying the connection +> ⚠️ **ngrok's free URL changes every time you restart it.** When that happens, the old URLs go dead and you must update **all three** in the Slack app again. A paid ngrok **static domain** (`ngrok http 8080 --domain=…`) avoids this — recommended if you demo often. -Once running, check the logs: +## Verifying the connection ```bash make logs | grep slack ``` -You should see: +On startup you should see (note: **not** "socket mode"): ``` -slack socket mode initialized bot_id=B... team=YourWorkspace -slack socket mode connected - bidirectional sync active +slack service initialized workspace=YourWorkspace +slack http event handler initialized bot_id=B... team=YourWorkspace ``` +Then send a message in an incident channel and watch a request arrive: + +```bash +make logs | grep "/api/v1/slack/events" +# POST /api/v1/slack/events status=200 +``` + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| Messages typed in Slack don't appear in the UI | No inbound request reaching the server | Tunnel down / wrong Events Request URL — restart ngrok, update the URL | +| Buttons show a ⚠️ and do nothing | Interactivity Request URL not set (or stale) | Set **Interactivity & Shortcuts** Request URL to `…/api/v1/slack/interactions` | +| Slack shows the Request URL won't verify, or logs show **403** | Signing secret in Regen ≠ Slack app's signing secret | Copy the exact Signing Secret from Basic Information into Settings → Slack | +| Slash commands do nothing | Slash command Request URL not set/stale | Set it to `…/api/v1/slack/commands` | +| `/regen` works but channel messages don't sync | Missing `message.channels` bot event | Add it under Event Subscriptions, reinstall if prompted | + ## Slack commands reference | Command | Description | |---------|-------------| -| `/incident new ` | Create a new incident | -| `/incident ack` | Acknowledge the current incident (in an incident channel) | -| `/incident resolve` | Resolve the current incident | -| `/incident status` | Show current incident status | +| `/regen new <title>` | Create a new incident | +| `/regen ack` | Acknowledge the current incident (in an incident channel) | +| `/regen resolve` | Resolve the current incident | +| `/regen status` | Show current incident status | +| `/regen note <text>` | Add a note to the incident timeline | +| `/regen list` | List open incidents | Commands also work by @mentioning the bot: `@Fluidify Regen ack` diff --git a/docs/self-hosting/environment-variables.md b/docs/self-hosting/environment-variables.md index 81dbb234..137f0228 100644 --- a/docs/self-hosting/environment-variables.md +++ b/docs/self-hosting/environment-variables.md @@ -36,7 +36,6 @@ Migrations run automatically on startup. No manual migration step required. |----------|---------|----------|-------------| | `SLACK_BOT_TOKEN` | — | Yes (Slack) | Bot token starting with `xoxb-` | | `SLACK_SIGNING_SECRET` | — | Yes (Slack) | Signing secret from your Slack app | -| `SLACK_APP_TOKEN` | — | Yes (Slack) | App-level token starting with `xapp-` — required for interactive buttons (Make me Lead, Add Note) | See [Connecting Slack](../getting-started/connecting-slack.md) for how to obtain these values. @@ -95,7 +94,6 @@ REDIS_URL=redis://redis:6379 # Slack SLACK_BOT_TOKEN=xoxb-... SLACK_SIGNING_SECRET=... -SLACK_APP_TOKEN=xapp-... # AI (optional) OPENAI_API_KEY=sk-... diff --git a/docs/self-hosting/kubernetes.md b/docs/self-hosting/kubernetes.md index af4f67dd..cdef4b29 100644 --- a/docs/self-hosting/kubernetes.md +++ b/docs/self-hosting/kubernetes.md @@ -82,8 +82,7 @@ kubectl create secret generic fluidify-regen-secrets \ --from-literal=DATABASE_URL="postgresql://..." \ --from-literal=REDIS_URL="redis://..." \ --from-literal=SLACK_BOT_TOKEN="xoxb-..." \ - --from-literal=SLACK_SIGNING_SECRET="..." \ - --from-literal=SLACK_APP_TOKEN="xapp-..." + --from-literal=SLACK_SIGNING_SECRET="..." ``` ## Migrations diff --git a/frontend/src/components/SlackSetupModal.tsx b/frontend/src/components/SlackSetupModal.tsx index e9c75bf4..28bd4f79 100644 --- a/frontend/src/components/SlackSetupModal.tsx +++ b/frontend/src/components/SlackSetupModal.tsx @@ -180,9 +180,10 @@ export function SlackSetupModal({ onClose, onConnected }: Props) { <div className="rounded-lg bg-amber-50 border border-amber-200 px-3 py-2.5 text-xs text-amber-800 space-y-1"> <p className="font-medium">Local / development environment detected</p> <p> - The manifest uses <strong>Socket Mode</strong> (no public URLs needed). Slack - will connect to your server over a persistent WebSocket — ideal for localhost. - For production, deploy to an HTTPS URL and re-run this wizard. + Slack can't reach <code>localhost</code>. Start a public tunnel to your backend + (<code>ngrok http 8080</code>) and use that HTTPS URL when creating the app — every + Request URL must point at the tunnel, not localhost. ngrok's free URL changes on + restart, so you'll re-paste it in the Slack app then. </p> </div> )} @@ -200,11 +201,10 @@ export function SlackSetupModal({ onClose, onConnected }: Props) { </li> {isLocal && ( <li> - Go to{' '} - <strong className="text-text-primary">Settings → Basic Information</strong> → - App-Level Tokens → generate a token with{' '} - <code className="bg-surface-secondary px-1 rounded">connections:write</code> scope - (needed for Socket Mode) + Make sure the manifest's Request URLs use your{' '} + <strong className="text-text-primary">tunnel URL</strong> (e.g.{' '} + <code className="bg-surface-secondary px-1 rounded">https://abc123.ngrok-free.app</code> + ), not localhost </li> )} </ol> @@ -215,8 +215,8 @@ export function SlackSetupModal({ onClose, onConnected }: Props) { <li>Bot scopes: channel management, messaging, user lookup</li> <li>User scopes: OpenID Connect (enables "Sign in with Slack")</li> <li>Slash command: /incident new | ack | resolve | status</li> - {!isLocal && <li>Event subscriptions: message sync, mentions</li>} - {isLocal && <li>Socket Mode: bidirectional real-time sync</li>} + <li>Event subscriptions: message sync, mentions</li> + <li>Interactivity: buttons (Make me Lead, Add Note)</li> </ul> </div>