Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/services/teams_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions deploy/helm/fluidify-regen/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 0 additions & 1 deletion deploy/helm/fluidify-regen/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ secrets:
redisURL: ""
slackBotToken: ""
slackSigningSecret: ""
slackAppToken: ""
openaiAPIKey: ""
teamsAppID: ""
teamsAppPassword: ""
Expand Down
1 change: 0 additions & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}
Expand Down
1 change: 0 additions & 1 deletion docker-compose.ha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}
Expand Down
155 changes: 101 additions & 54 deletions docs/getting-started/connecting-slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <title>` | 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`
2 changes: 0 additions & 2 deletions docs/self-hosting/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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-...
Expand Down
3 changes: 1 addition & 2 deletions docs/self-hosting/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/components/SlackSetupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
)}
Expand All @@ -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>
Expand All @@ -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>

Expand Down
Loading