Slack
Install Pact into your Slack workspace to fan CRM events into channels, DM users on @mentions, run /pact slash commands, ingest thread replies as comments, and post AI meeting summaries.
Slack
The Slack integration connects your workspace to Pact over OAuth and drives five surfaces:
channel routing for CRM events, per-user DMs on @mentions, a /pact slash command, thread
replies that flow back into Pact as comments, and post-meeting AI summaries posted to a
channel. Everything is tenant-scoped, and outbound delivery is gated behind a feature flag
that ships off by default.
Live and enforced
Shipped. Workspace OAuth, channel routes, the Events API webhook, and the slash-command
handler live in api/routes/integrations_slack.py; the domain logic is under
core/integrations/slack/. Meeting summary fan-out (core/video_meetings/notify.py) was
delivered alongside recording-consent enforcement.
Installation
- 1
Configure the Slack app credentials
Pact needs
SLACK_CLIENT_IDandSLACK_CLIENT_SECRET(per-tenant BYOK credentials, or platform env vars). Token encryption viaAUTH_TOKEN_ENCRYPTION_KEYmust be configured —/startreturns HTTP 500 otherwise. - 2
Start OAuth
An admin calls
POST /v1/integrations/slack/start. Pact persists a single-use state token and returns anauthorize_url. The requested bot scopes arechat:write,im:write, andusers:read.email; the per-user grant requestschat:writeso a user's own DMs are auditable as themselves. - 3
Complete the redirect
Slack redirects to
GET /v1/integrations/slack/callback. This route takes no auth header — it recovers the tenant from the single-use state token, exchanges the code, and stores the encrypted workspace bot token (xoxb-). If the installer also granted the user scope in the same flow, Pact links theirtenant_usersrow immediately viausers.infoemail lookup, so they don't have to click "Link Slack" separately. - 4
Enable delivery
Outbound sends are gated by the per-tenant feature flag
SLACK_DELIVERY_ENABLED, which is OFF by default so a freshly connected workspace never starts messaging real users unexpectedly. Turn it on when you're ready.
Delivery is off until you enable the flag
Until SLACK_DELIVERY_ENABLED is on for your tenant, notifications are still
recorded (in the notifications table) but no Slack message is actually pushed. The dispatcher
returns a result with notes="delivery_disabled" for observability. This is a
deliberate safety default, not a limitation.
Channel routing for CRM events
Map Pact event types to Slack channels via /v1/integrations/slack/channel-routes (list /
create / delete). When an event fires, core/integrations/slack/dispatcher.py posts a
formatted message to every active route and stores a thread link so replies can be ingested.
The known event types (GET /v1/integrations/slack/event-types) are:
deal.stage_changed account.high_value
enrichment.completed compliance.flag_raised
billing.trial_expiring billing.subscription_changed
@mention
Each type has its own formatter — a stage change posts a :bar_chart: card, enrichment a
:sparkles: card, a compliance flag a :warning: card, and so on.
DMs on @mention
When someone @mentions a teammate on a Pact record, core/integrations/slack/notifier.py
tries to DM them. It resolves a Slack identity in priority order: (1) the user's explicitly
linked slack_user_id, then (2) a users.lookupByEmail call via the workspace bot token. If
neither resolves, the notification is marked no_slack_identity and Pact moves on — the
mention itself is never blocked by a Slack failure.
The /pact slash command
POST /v1/integrations/slack/commands handles /pact subcommands, validated by the Slack
signing secret. Tenant is resolved from the workspace team_id. All queries are tenant-scoped:
| Command | What it does |
|---|---|
/pact deals | Live pipeline overview — open value, stage breakdown, largest open deals |
/pact deal <name> | Find a specific deal by name |
/pact accounts <query> | Search accounts by name or domain |
/pact contacts <query> | Search contacts by name or job title |
/pact ai <prompt> | Grounded answer over your open pipeline via Pact AI (fast tier) |
/pact ai needs an AI key
The AI subcommand makes a real (fast-tier) model call grounded in your open pipeline. If the workspace hasn't configured a Claude key in Pact admin, it degrades honestly with a message telling you to add one — it never fabricates an answer.
Thread replies become comments
Pact registers a Slack Events API webhook at POST /v1/integrations/slack/events (verified by
the SLACK_SIGNING_SECRET; it also answers the url_verification challenge during app setup).
When a message is a reply in a thread Pact started, and that thread is linked to a record,
the reply is written back into Pact as a comment tagged [via Slack] with author
Slack:<user_id>. Bot messages and root messages are ignored to prevent echo loops.
Post-meeting AI summaries
Meeting intelligence config exposes post_meeting_slack_enabled and a slack_channel_id. When
enabled and the summary is AI-powered, core/video_meetings/notify.py::dispatch_meeting_summary_slack
posts a Block Kit summary (title, action items, decisions, and a deep link to the meeting's
intelligence page) into the configured channel, reusing the workspace bot token. Like every
other Slack path it's best-effort — a missing token or channel returns a skipped result and is
logged, never raised.
Auditing and status
GET /v1/integrations/slack/status— workspace connection state, team name, bot user id, delivery-flag state, and counters (dm_sent,dm_failed, etc.).GET /v1/integrations/slack/audit— the slash-command audit log (who ran what, and how many results came back).DELETE /v1/integrations/slack— disconnect the workspace.
Two token tiers
The workspace bot token (xoxb-) lives encrypted on the integrations row; per-user
tokens (xoxp-) live on tenant_users so DMs survive a workspace-wide bot
uninstall. When DMing, Pact prefers the user's own token so the message reads as the user
talking to themselves, falling back to the bot token otherwise.