Slack
Connect a Slack workspace to answerLoops via 1-click OAuth, an optional Events API webhook, or polling mode with no admin approval required.
Overview
answerLoops connects to Slack in one of two ways, and the two are independent of each other:
- OAuth install — an admin authorizes answerLoops from the workspace's Add to Slack screen. answerLoops receives a bot token (
xoxb-…) and a channel picker loads automatically. Message delivery into answerLoops then happens either via the Events API webhook (real-time) or, on self-hosted deployments only, by falling back to polling. - Polling mode (manual setup) — a bot token, team ID, and channel IDs are pasted in by hand, with no OAuth authorization and no public webhook at all. This is the path for someone who can't get a Slack admin to click "Authorize," or who can't expose a public URL for Slack to call.
Every connected channel appears in the unified ticket list with a Slack source badge. Slack is a Standard-plan-and-above feature — saveSlackIntegrationAction, saveSlackChannelsAction, and the /api/slack/install route all gate on the slack_integration entitlement and return an error before touching the workspace if the org's plan doesn't include it.
Automatic Deflections is off by default for every newly connected platform. While it's off, even a high-confidence AI answer is held as a draft on the dashboard awaiting Approve/Edit/Dismiss — Slack gets a brief acknowledgment reaction/reply instead of the real answer. Turn it on per-workspace in Integrations → Slack → Edit channels → Automatic Deflections toggle → Save channels once you've reviewed enough approved drafts to trust the AI's answers going out unsupervised.
Option 1: 1-click OAuth (recommended)
- In Integrations → Slack (or the onboarding wizard), click Add to Slack
- answerLoops requests the
channels:history,channels:read,channels:join,chat:write,reactions:write, andusers:readscopes and redirects to Slack's authorization screen - An admin picks the workspace and approves
- Slack redirects back to
/api/slack/callback, which exchanges the code for a bot token and stores it (along with the team ID and an internally-generatedbot_secretused to authenticate the bot's own traffic back to answerLoops) - The channel picker opens automatically, listing every public channel in the workspace via
conversations.list - Select the channels to monitor and click Save channels
No token to copy, no manifest to upload. The state parameter round-tripped through the OAuth redirect expires after 10 minutes — if you leave the authorization screen open too long before approving, you'll be sent back with an invalid_state error and need to click Add to Slack again.
Saving channels joins the bot automatically
Slack never adds a bot to a channel just because a scope was granted — the bot has to explicitly call conversations.join. Both saveSlackChannelsAction (editing channels post-connect) and saveSlackIntegrationAction (manual setup) call this for every selected channel as part of the save, so picking a channel and clicking Save is enough to make it work immediately — there's no separate "invite the bot" step for public channels.
Private channels are the one exception. Slack doesn't let any bot self-join a private channel — this is a platform limit, not something answerLoops works around. If a save includes a private channel, the join call fails with not_in_channel-adjacent errors and the UI shows a warning like:
Joined 2/3 channels automatically. Private channels need a manual invite in Slack (Channel → Integrations → Add apps): C09876ZYXWV
You still need to /invite @answerLoops (or add the app via that channel's Integrations → Add apps) for any private channel yourself.
Events API webhook (real-time delivery)
The Events API webhook is what gives OAuth-connected workspaces instant delivery. It's automatic on managed cloud (the platform has one shared webhook URL already configured) and requires one extra step for self-hosted deployments:
- In your Slack app's Event Subscriptions, set the Request URL to
{YOUR_DOMAIN}/api/slack/events— the self-hosted Integrations page surfaces this exact URL once Slack is connected - Subscribe to the
message.channelsandreaction_addedbot events - Slack sends a
url_verificationchallenge on save;/api/slack/eventsanswers it before any signature check runs - Every subsequent event is verified against
SLACK_SIGNING_SECRETusing thex-slack-request-timestampandx-slack-signatureheaders — a request that fails verification gets a403, and an event for a team answerLoops has no integration record for is rejected the same way
Once wired up, new messages create tickets in real time, and 👍/👎 or 1️⃣–5️⃣ reactions on a bot reply are read as feedback votes or CSAT ratings respectively.
Option 2: Polling mode (no admin, no webhook)
Self-hosted only — every managed-cloud org already gets real-time delivery through the shared Events API webhook, so cloud never starts the polling loop at all. Polling exists for operators who can't get Slack admin approval for an OAuth install, or who can't expose a public webhook URL to Slack for security-review or firewall reasons.
- Go to Integrations → Slack and click Set up manually instead (only shown on self-hosted deployments, next to Add to Slack)
- Paste a bot token (
xoxb-…), the workspace's Team ID (T…), and one or more channel IDs - A signing secret is optional here — only required if you also plan to wire up the Events API webhook alongside polling
- answerLoops calls
conversations.joinon every listed channel the same way OAuth does, then starts pollingconversations.historyon that channel everySLACK_POLL_INTERVAL_SECONDS(default 60, minimum recommended 30)
A channel's first poll never backfills history. The poller seeds its cursor to the newest message already in the channel and tickets nothing on that pass — exactly like Discord's gateway, which never sees anything posted before the bot joined. Only messages posted after the first poll become tickets. This is deliberate: without it, connecting Slack to an active community channel would flood the ticket list with the channel's entire recent history on day one.
Why some deployments choose polling over OAuth:
- No inbound HTTP from Slack to your servers — the bot only ever calls out to
slack.com - No public webhook URL required, so nothing new to expose past a firewall
- Narrower security review scope: one outbound API call per poll interval, versus an inbound endpoint that has to verify every request's signature
- A read-only-scoped token is enough — no OAuth app, no admin authorization flow
SLACK_POLL_INTERVAL_SECONDS=60 # default. Minimum recommended: 30.Polling and the Events API webhook aren't mutually exclusive on self-hosted deployments — a workspace connected via manual token entry can still have a signing secret set and receive webhook events if you wire up Event Subscriptions for it. In practice most operators pick one or the other.
Feature comparison
| OAuth + Events API webhook | OAuth (self-hosted, polling) | Manual polling | |
|---|---|---|---|
| Admin authorization needed | Yes | Yes | No |
| Public webhook URL required | Yes | No | No |
| Message latency | Instant | Poll interval (default 60s) | Poll interval (default 60s) |
| Channel picker | Automatic | Automatic | Manual (paste IDs) |
| Signing secret needed | Yes, to verify inbound events | No | Optional |
| Auto-joins selected channels | Yes (public only) | Yes (public only) | Yes (public only) |
| Available on managed cloud | Yes (default) | No | No |
Escalation, confidence, and deflection settings
The channel picker's save form (Integrations → Slack → Edit channels) also sets three per-workspace fields stored on the integration record:
- Escalation User Group ID (optional) — a Slack user group or user ID (
S…orU…) pinged when the AI's confidence is below threshold - Confidence threshold (0–1, default
0.8) — the AI answer confidence required before answerLoops treats a question as answerable at all - Automatic Deflections — off by default; see the callout above
These map directly to the escalation_role_id, confidence_threshold, and auto_deflect_enabled fields on the integration record, alongside bot_token, team_id, and channel_ids.
Environment variables reference
| Variable | Required | Description |
|---|---|---|
SLACK_CLIENT_ID | For OAuth install | OAuth app Client ID — enables the Add to Slack 1-click flow |
SLACK_CLIENT_SECRET | For OAuth install | OAuth app Client Secret, paired with SLACK_CLIENT_ID |
SLACK_SIGNING_SECRET | For Events API webhook | Verifies inbound webhook payloads at /api/slack/events; platform-wide, not per-workspace |
SLACK_POLL_INTERVAL_SECONDS | No | Polling interval in seconds. Default 60, minimum recommended 30 |
AUTH_URL | Yes (self-host) | Public app URL — Slack redirects to {AUTH_URL}/api/slack/callback after authorization |
BOT_TARGET_URL | For polling | Internal URL the poller uses to forward ingested messages to the app's /api/ingest — no trailing slash |
Add {AUTH_URL}/api/slack/callback to your Slack app's OAuth & Permissions → Redirect URLs before anyone connects via OAuth.