Slack
Slack captures collaboration that doesn't show up in tickets or commits: helping teammates unblock, staying responsive to mentions, and maintaining a consistent presence in the channels where work happens. The headline metric is help_replies_given — replies in threads that match help-seeking patterns.
What we pull
The ingester scans every public channel the Slack app has access to and records per-message metadata for each employee. No message text is stored. What's stored per event:
is_thread_reply— whether the message is a reply in an existing threadis_help_reply— whether the reply landed in a thread that was a help request. Detection looks at the parent message of the thread (keywords like "anyone know", "stuck", "blocked on", "can someone", "how do I", etc.) OR at the channel name — any reply in a channel matching the help-channel pattern (default^help-, case-insensitive) counts.positive_reactions— count of positive reactions the message received (+1,heart,fire,rocket,100, etc.)reaction_count— total reactionshas_mention— whether the message @-mentions someoneis_business_hours— whether the message was sent 9AM–6PM Mon–Fri in the org's timezone
A separate response time analysis runs via search.messages, measuring how quickly the employee responds to @mentions during business hours. This produces avg_minutes, median_minutes, p90_minutes, and under_15min_pct.
Public channels only. Private channels and DMs are never accessed. This is hard-coded and not configurable.
Connecting
Slack is an org-wide install.
- Go to
/admin?tab=integrations, find the Slack card. - Click Connect. You'll authorize the PerfCopilot Slack app for your workspace.
- The app requires
channels:history,channels:read,users:read, andsearch:readscopes. The OAuth flow requests all of these. - Map each employee's Slack member ID (visible in their Slack profile — click the three dots, then "Copy member ID") in the unmapped-employees list on the card.
User token for search.
search.messagesrequires a user-scoped OAuth token. PerfCopilot's OAuth flow requests this alongside the bot token. Legacy PAT-based installs used the PAT as both, which works but is less precise. If you installed before 2026-05-01, reconnecting will pick up the user token.
Optional: help-channel pattern
The Slack card on /admin?tab=integrations exposes one optional setting: Help channel pattern (regex). Any reply in a channel whose name matches this regex counts as a help reply regardless of the parent message's wording — useful for dedicated rooms like #help-eng, #ask-platform, etc.
- Default (blank):
^help-(case-insensitive). So#help-eng,#help-data,#Help-Frontendall match. - Custom example:
^(help|ask)[-_]to also cover#ask-eng. - Invalid regex falls back to the default silently — the ingester logs a warning but doesn't abort.
Replies outside matching channels still count when the thread's parent message contains help-seeking keywords. The two arms are OR'd.
What hits a review
[SLACK DATA]
total_messages: 94
help_replies_given: 18
positive_reactions: 41
avg_response_min: 11.2
under_15min_pct: 73
channels_active: 6
business_hours_pct: 88
Plus cohort medians for help_replies_given and avg_response_min in the [BASELINES] block.
Troubleshooting
"Slack signals show zero messages"
- No Slack member ID mapped. The ingester filters channel history by user ID. Without it, every message in every channel is skipped. Check the Slack card for unmapped employees.
- App not invited to channels. The bot must be a member of a channel to read its history. Use
/invite @PerfCopilotin the channels you want tracked, or install the app to all channels from Slack's App Directory page. - All channels are private. The ingester only reads public channels. If your org uses only private channels, Slack signals will always be zero.
"Response time shows no data"
search.messages requires a user-scoped token. If the card shows response_stats: {}, it means the search step ran with a bot-only token. Reconnect the Slack integration to pick up the user token.
"Help replies count seems too high / too low"
The is_help_reply flag is set when either:
- The thread's parent message contains help-seeking keywords (the ingester fetches the parent once per thread and caches it, so a 50-reply thread costs one extra API call, not fifty), or
- The reply landed in a channel matching your help channel pattern (default
^help-).
If counts look off:
- Too high in a busy
#help-*room? The channel arm fires on every reply in that channel — that's the design. Narrow the regex if you only want certain rooms (e.g.^help-(eng|data)$). - Too low in a non-help channel? The keyword arm matches the literal parent text. If your team phrases questions in a way none of the default keywords catch ("Anybody ran into…?"), open a support ticket — we can extend the keyword set or you can route those threads into a channel that matches your help pattern.
Privacy notes
- Message text is never stored. Metadata only.
positive_reactionscounts are stored but individual reaction names are not linked back to who reacted.- Business hours filtering uses the org timezone set in Settings → Organization. If that's not set, UTC is assumed, which may misclassify messages near 9AM/6PM boundaries.
- The ingester limits Slack API concurrency to 5 parallel channel scans to stay within Slack's per-workspace rate limits.