Build a Slack Bot
Receive Slack events, verify signatures, and reply to mentions.
This guide builds a Slack bot that receives events over HTTP, verifies each
request's signature, answers Slack's URL-verification handshake, and replies to
@-mentions by name, following
samples/slack-bot.yaml.
Set up the Slack app
Create a Slack app at api.slack.com/apps. Under
OAuth & Permissions, give the bot the chat:write, users:read, and
users:read.email scopes, then install it to your workspace and copy the
bot token (xoxb-...).
Under Basic Information, copy the signing secret. Slack signs every request with it, and the flow verifies that signature.
Under Event Subscriptions, subscribe the bot to the app_mention event and
point the Request URL at this service's /slack/events route. While
developing, expose your local port with a tunnel such as ngrok. Slack sends a
one-time verification challenge to the URL when you save it; the flow below
answers it automatically.
Export the credentials and run:
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_SIGNING_SECRET=...
octo run --config samples/slack-bot.yamlThe connectors
The config declares the HTTP server that receives events and a slack connector
holding the credentials:
connectors:
- name: api
type: http
settings:
port: ${HTTP_PORT}
- name: slack
type: slack
settings:
botToken: ${SLACK_BOT_TOKEN}
signingSecret: ${SLACK_SIGNING_SECRET}Capture the raw request
Slack signs the exact request bytes, so rawBodyVar copies the raw body
into a variable and headers captures Slack's two signature headers.
source:
connector: api
type: http
settings:
path: /slack/events
headers: [X-Slack-Signature, X-Slack-Request-Timestamp]
rawBodyVar: rawBodyVerify, then handle
The first block authenticates the request; the rest branches between Slack's one-time handshake and real events:
process:
# 1. Authenticate the request (aborts on a bad or stale signature) and
# flag Slack's URL-verification handshake.
- type: slack-verify-request
name: verify
settings:
connector: slack
# 2. Either echo the URL-verification challenge, or handle the event.
- type: if
name: challenge-or-event
condition: has(vars.slackChallenge)
then:
process:
# Slack's one-time handshake: reply 200 with the challenge value.
- type: set-payload
settings:
value: '{"challenge": body.challenge}'
else:
process:
# Keep only @-mentions, ignoring the bot's own messages.
- type: slack-event
name: mentions
settings:
eventTypes: [app_mention]
filter: body.botId == null
# Resolve the mentioning user (users.info) into vars.slackUser.
- type: slack-lookup-user
name: whois
settings:
connector: slack
by: id
user: body.user
# Reply in the same channel, greeting the user by name.
- type: slack-send-message
name: reply
settings:
connector: slack
target: body.channel
text: '"thanks " + vars.slackUser.real_name + ", you said: " + body.text'slack-verify-request recomputes the HMAC signature over vars.rawBody with
the connector's signing secret and aborts the flow on a mismatch or a stale
timestamp. When the request is Slack's url_verification handshake, it sets
vars.slackChallenge so the flow can branch; the challenge branch replies with
{"challenge": body.challenge}, which is what Slack expects to confirm your
Request URL.
slack-event filters the event stream: eventTypes keeps only app_mention,
and the CEL filter drops messages sent by bots (body.botId == null) so the
bot never answers itself. It also flattens the event into body.user,
body.channel, body.text, body.ts and body.threadTs, with the untouched
original under body.raw.
slack-lookup-user calls users.info for the mentioning user and stores the
user object in vars.slackUser (by: email, the default, looks up by email
address). slack-send-message posts to target, a channel ID such as
body.channel, and stores the sent message's timestamp in vars.slackTs.
Reply in a thread
To answer in a thread, pass the triggering message's timestamp as threadTs:
- type: slack-send-message
settings:
connector: slack
target: body.channel
threadTs: body.ts
text: '"on it!"'For a message that is already in a thread, use its body.threadTs so the reply
lands in the same thread rather than starting a new one.
Try it
Mention the bot in a channel it has been invited to:
@yourbot hello there
The bot replies in-channel: thanks Ada Lovelace, you said: <@U123ABC> hello there.
Slack expects a 2xx within 3 seconds, or it retries the event. Acknowledge
first and offload slow work (LLM calls, database writes) with
queue-dispatch. See Events and Queues.
Where to go next
The Slack AI Agent guide keeps this receive-verify-filter skeleton and swaps the echo for an AI agent with memory and tools.