Skip to content

Slack Integration

BATCH IQ integrates with Slack to send notifications and enable conversational interactions via thread replies. This guide covers setup and migration to a different Slack workspace.

If thread replies aren’t working, check these three “adding” steps first:

  1. Add Bot to Channel - Invite the bot: /invite @YourBotName in each channel
  2. Add Event Subscriptions - Add message.channels event, then Reinstall to Workspace
  3. Add Bot Scopes - Add channels:history scope, then Reinstall to Workspace

After each “add” step, you MUST click “Reinstall to Workspace” for changes to take effect!

When moving to a different Slack account/instance:

  • Create new Slack app in new workspace

    • Go to https://api.slack.com/apps
    • Create new app → “From scratch”
    • Name it (e.g., “QC Test Agent Notifications”)
    • Select the new workspace
  • Configure bot scopes ⚠️ CRITICAL FOR THREAD REPLIES

    • Go to “OAuth & Permissions”
    • Add Bot Token Scopes (click “Add an OAuth Scope”):
      • chat:write - Send messages
      • chat:write.public - Send to channels the bot isn’t in (recommended)
      • channels:history - REQUIRED - Read channel history (for thread replies)
      • groups:history - REQUIRED - Read private channel history (if using private channels)
    • IMPORTANT: After adding scopes, you MUST click “Reinstall to Workspace” at the top of the page for changes to take effect
  • Install app to workspace

    • Click “Install to Workspace”
    • Authorize the app
    • Copy the Bot User OAuth Token (starts with xoxb-)
  • Set up Event Subscriptions ⚠️ CRITICAL FOR THREAD REPLIES

    • Enable Events (toggle ON)
    • Request URL: https://your-convex-deployment.convex.site/api/slack/events
    • Scroll down to “Subscribe to bot events” section
    • Add Bot User Event (click the button):
      • message.channels - REQUIRED - Messages in public channels
      • message.groups - REQUIRED - Messages in private channels (if using private channels)
    • IMPORTANT: After adding events, you MUST click “Reinstall to Workspace” at the top of the page
    • Verify the webhook shows “Verified ✓” (green checkmark)
    • Copy the Signing Secret from “Basic Information” → “App Credentials”
  • Update Convex environment variables

    Terminal window
    cd packages/backend
    # Set the new bot token
    npx convex env set SLACK_TOKEN xoxb-your-new-token-here
    # Set the new signing secret
    npx convex env set SLACK_SIGNING_SECRET your-new-signing-secret
    # Optional: Set custom default channel (if different from hardcoded channels)
    npx convex env set SLACK_CHANNEL #your-default-channel
    # Verify they're set
    npx convex env list
  • Create/verify target channels exist

    • #qc-agent-lims-managers (for LIMS-related notifications)
    • #qc-agent-mes-managers (for MES-related notifications)
    • Or update channel names in code if using different names
  • Invite bot to all required channels ⚠️ CRITICAL FOR THREAD REPLIES

    • The bot must be a member of the channel to receive events
    • In each channel, type: /invite @YourBotName
    • Verify the bot appears in the channel member list
    • Without this step, the bot cannot receive thread replies!
  • Test notification delivery

    Terminal window
    cd packages/backend
    ./test-slack.sh
  • Test thread reply functionality

    • Post a notification to Slack
    • Reply in the thread
    • Verify the bot responds
  • (Optional) Migrate/clear database records

    • Review agent_thread_contexts table for Slack thread mappings
    • Review slack_processed_events table for event deduplication records
    • Clear or migrate as needed

Notifications are automatically routed to channels based on event type:

  • Agent proposals#qc-agent-lims-managers
  • Approval decisions#qc-agent-mes-managers
  • LIMS events#qc-agent-lims-managers
  • MES events#qc-agent-mes-managers

To customize channel routing, update the getTargetChannel() method in packages/backend/convex/notifications/channels/slackChannel.ts.

All Slack configuration is done via Convex environment variables (not .env.local):

VariableDescriptionRequired
SLACK_TOKENBot User OAuth Token (xoxb-...)Yes
SLACK_SIGNING_SECRETSigning secret for webhook verificationYes
SLACK_CHANNELDefault fallback channelNo

After setup, verify the integration:

Terminal window
# Test Slack API connection
npx convex run notifications/test:testSlackIntegration --no-push
# Test Slack channel implementation
npx convex run notifications/test:testSlackChannel --no-push

Or use the convenience script:

Terminal window
cd packages/backend
./test-slack.sh

If you see SLACK_TOKEN not configured, ensure you’ve set it in Convex:

Terminal window
npx convex env set SLACK_TOKEN xoxb-your-token
  • Verify you’re using a Bot User OAuth Token (starts with xoxb-)
  • Ensure the app is installed to the workspace
  • Check that required scopes are added and app is reinstalled
  • Verify SLACK_SIGNING_SECRET is set correctly
  • Check that the webhook URL in Slack matches your Convex deployment URL
  • Ensure the webhook URL is publicly accessible

If notifications work but thread replies don’t, check these items in order:

Most common issue - The bot must be subscribed to message events:

  1. Go to your Slack app → Event Subscriptions
  2. Verify Enable Events is turned ON
  3. Check Request URL matches: https://your-deployment.convex.site/api/slack/events
  4. Scroll down to “Subscribe to bot events” section
  5. CRITICAL: Click “Add Bot User Event” and add:
    • message.channels (required for public channels)
    • message.groups (required for private channels)
  6. IMPORTANT: After adding events, click “Reinstall to Workspace” at the top of the page
  7. Verify the webhook shows “Verified ✓” (green checkmark)
  8. If events are missing, the bot will NOT receive thread replies!

The bot needs these scopes to read and respond to threads:

  1. Go to OAuth & Permissions
  2. Under Bot Token Scopes, click “Add an OAuth Scope” and add:
    • chat:write - Send messages
    • chat:write.public - Send to channels bot isn’t in
    • channels:history - REQUIRED to read thread messages
    • groups:history - REQUIRED for private channels
  3. CRITICAL: After adding scopes, click “Reinstall to Workspace” at the top of the page
  4. Without channels:history, the bot cannot read thread messages!

CRITICAL STEP - The bot must be a member of the channel to receive events:

  1. In Slack, go to the channel where notifications are posted
  2. Type: /invite @YourBotName (replace with your actual bot name)
  3. Verify the bot appears in the channel member list
  4. If the bot is not in the channel, it will NOT receive thread reply events!
  5. You must invite the bot to every channel where you want thread replies to work

Check Convex logs for signature errors:

  1. Check your Convex dashboard logs
  2. Look for [SlackHandler] Invalid signature or [SlackHandler] Missing signature headers
  3. Verify SLACK_SIGNING_SECRET is set correctly:
    Terminal window
    npx convex env list | grep SLACK_SIGNING_SECRET
  4. Copy the Signing Secret from Slack app → Basic InformationApp Credentials
  5. Update if needed:
    Terminal window
    npx convex env set SLACK_SIGNING_SECRET your-signing-secret

Look for these log messages when you reply to a thread:

Good signs (events are being received):

  • [SlackHandler] Received thread reply, scheduling async:
  • [SlackHandler] Processing message async:

Bad signs (events being filtered):

  • [SlackHandler] Ignoring top-level message (not a thread reply) - You replied to the channel, not the thread
  • [SlackHandler] Ignoring duplicate event: - Event was already processed
  • [SlackHandler] Invalid signature - Signature verification failed
  • [SlackHandler] Missing signature headers - Headers not present

Errors to watch for:

  • [SlackHandler] Failed to fetch thread parent: - Bot can’t read thread (check scopes)
  • [SlackHandler] Failed to post response: - Bot can’t reply (check scopes/permissions)

The bot only responds to thread replies, not top-level messages:

  1. Click the notification message to open the thread
  2. Type your reply in the thread (not in the main channel)
  3. The reply should appear indented under the notification

Verify the webhook URL is accessible:

Terminal window
# Test if the endpoint is reachable (should return 400 for invalid JSON, not 404)
curl -X POST https://your-deployment.convex.site/api/slack/events \
-H "Content-Type: application/json" \
-d '{"test": "data"}'

If you get 404, the route isn’t configured. If you get 401, signature verification is working (expected without proper signature).

  1. Go to Slack app → Event Subscriptions
  2. Scroll down to Recent Events
  3. Reply to a thread
  4. Check if a new event appears in the list
  5. If no events appear, the webhook URL might be wrong or unreachable

Ensure all required variables are set in production:

Terminal window
# Check all Slack-related env vars
npx convex env list | grep SLACK
# Should show:
# SLACK_TOKEN=xoxb-...
# SLACK_SIGNING_SECRET=...
  • Bot not invited to the channel (most common - bot must be a member!)
  • Added scopes but didn’t reinstall the app (must click “Reinstall to Workspace”)
  • Added event subscriptions but didn’t reinstall the app (must click “Reinstall to Workspace”)
  • ❌ Replying to the channel instead of the thread
  • ❌ Webhook URL pointing to wrong deployment
  • ❌ Using user token instead of bot token
  • ❌ Missing channels:history scope
  • ❌ Missing message.channels event subscription