Set up a Slack app for OutcomeCI
Create a Slack app that starts an OutcomeCI workflow when someone mentions it or sends it a direct message, and lets the workflow answer in the thread.
A Slack app turns a message into a workflow run. Mention the app in a channel, or send it a direct message, and OutcomeCI starts a run with that message as its input. The workflow can then reply in the thread, read the conversation that follows, and wait for a reaction before it does anything consequential.
By the end of this guide you will have:
- a Slack app with only the scopes OutcomeCI uses;
- its bot token and signing secret in your OutcomeCI Vault; and
- a workflow that answers in the thread of every message that mentions the app.
How the pieces fit
Two credentials do two different jobs:
| Credential | Where Slack shows it | What OutcomeCI does with it |
|---|---|---|
| Signing secret | Basic Information, under App Credentials | Verifies that each incoming event really came from Slack |
Bot token (xoxb-) |
OAuth & Permissions, after you install the app | Posts messages, reads threads, reactions, and shared files |
Slack sends events to your workflow's webhook URL. OutcomeCI checks each request's signature with the signing secret, ignores events the workflow does not listen for, and queues one run per message. The signing secret is used only for that check; no step can read it.
Order matters here. When you give Slack a request URL, Slack sends a verification request and expects an answer straight away. OutcomeCI can only answer once the signing secret is in the Vault and granted to the workflow, so the request URL is the last thing you set.
What you need
- An OutcomeCI account and a workspace.
- The OutcomeCI CLI:
pip install outcomeci-cli, orbrew install outcomeci/tap/outcomeci-cli. Sign in withoci auth login. - A Slack workspace where you can create and install apps.
- Optional: the Slack CLI, for the automated setup in step 2.
1. Write the workflow
Create outcome.yml. The trigger names the Slack receiver, the secret it
verifies with, and the events that start a run:
apiVersion: outcomeci.workflow/v1
name: slack-assistant
trigger:
webhook: {uses: slack, auth: secrets.slack_signing, events: [mention, dm]}
secrets:
slack_signing: vault:slack/signing-secret
slack: vault:slack/bot-token
apis:
slack: {uses: slack, auth: secrets.slack}
reasoning:
default: {runner: claude, model: claude-opus-5-5}
steps:
- reply:
from: trigger
reason: >
Read the message that started this run and reply in its thread with a
short, useful answer.
can:
- slack.post: {channel: trigger.channel, thread_ts: trigger.ts}The slack.post grant pins every reply to the channel and thread the message
came from. If the agent tries to post anywhere else, the call is refused.
Each run's trigger carries the message's channel, user, text, and
ts. Two events can start a run:
| Event | Starts a run for |
|---|---|
mention |
A top-level message that @mentions the app, in a channel it is in |
dm |
A top-level direct message to the app |
Replies in a thread never start a new run. They belong to the conversation the thread already carries, which a workflow reads with a converse step.
Validate the file, then sync it to your workspace:
oci validate
oci workflow sync outcome.yml --workspace-id WORKSPACE_ID --createSync prints the new workflow's ID. You need it in step 3.
2. Create the Slack app
Pick one of two routes. Both produce the same app.
With the OutcomeCI CLI
oci integration slack setup writes a manifest with exactly the scopes the
Slack connector uses, then creates and installs the app through the Slack CLI.
It signs you in to Slack first if needed.
oci integration slack setup --name "Acme Assistant"Leave out the request URL on this first pass. You add it in step 4.
In the Slack dashboard
Go to api.slack.com/apps, choose Create New App, then From a manifest. Pick your workspace and paste:
{
"display_information": {
"name": "Acme Assistant",
"description": "Starts OutcomeCI workflows and talks with them in threads."
},
"features": {
"app_home": {
"home_tab_enabled": false,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"bot_user": {
"display_name": "Acme Assistant",
"always_online": false
}
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"channels:history",
"chat:write",
"files:read",
"groups:history",
"im:history",
"mpim:history",
"reactions:read"
]
}
},
"settings": {
"org_deploy_enabled": false,
"socket_mode_enabled": false,
"token_rotation_enabled": false
}
}Then open OAuth & Permissions and choose Install to Workspace.
Each scope maps to something the workflow can do:
| Scope | Lets the app |
|---|---|
app_mentions:read |
Receive mention events |
im:history |
Receive dm events, and read DM threads |
chat:write |
Post messages and thread replies |
channels:history, groups:history, mpim:history |
Read threads in public channels, private channels, and group DMs |
reactions:read |
See reactions, for approval steps that wait on an emoji |
files:read |
Open files shared in a conversation, such as a screenshot |
messages_tab_enabled lets people send the app direct messages. Turn it off,
and drop im:history, if the workflow listens only for mentions.
3. Store the credentials
Both credentials go into the workspace Vault, granted to the workflow from step 1. Values are write-only: once stored, they never come back to the browser, the CLI, or an agent.
Bot token. If you used the OutcomeCI CLI in step 2, copy the installed app's token straight across:
oci integration slack sync-credentials --cloud \
--workspace-id WORKSPACE_ID --workflow-id WORKFLOW_IDOtherwise, copy the Bot User OAuth Token from OAuth & Permissions and store it yourself:
printf %s "$SLACK_BOT_TOKEN" | oci vault put slack/bot-token \
--workspace-id WORKSPACE_ID --value-stdin --workflow-id WORKFLOW_IDSigning secret. Copy it from Basic Information, under App Credentials:
printf %s "$SLACK_SIGNING_SECRET" | oci vault put slack/signing-secret \
--workspace-id WORKSPACE_ID --value-stdin --workflow-id WORKFLOW_IDPiping through printf keeps both values out of your shell history. You can
also add each one in the dashboard: open Vault, choose
Add credential, and select the workflow under its access.
Finally, connect the agent account the workflow reasons with under Vault, then Agents.
4. Connect Slack to the workflow
In the OutcomeCI dashboard, open the workflow's Settings. Under Webhooks, choose Enable webhook and copy the URL.
Now give that URL to Slack.
With the OutcomeCI CLI, run setup again with the URL and the events your trigger lists:
oci integration slack setup --name "Acme Assistant" \
--request-url "$WEBHOOK_URL" --event mention --event dmIn the Slack dashboard, open Event Subscriptions and turn on
Enable Events. Paste the webhook URL as the Request URL and wait for
Slack to show it as verified. Under Subscribe to bot events, add
app_mention and message.im, then save. Slack may ask you to reinstall the
app.
5. Try it
Invite the app to a channel, then mention it:
/invite @Acme Assistant
@Acme Assistant what does this workflow do?A run appears under the workflow in the dashboard within a few seconds, and
the answer lands in the thread of your message. Send the app a direct message
to try the dm event.
Where to take it next
A reply is the smallest useful workflow. From here you can:
- hold a conversation in the thread before acting, with a converse step;
- wait for an approving reaction before anything changes, with an await step; and
- add the GitHub connector to turn an approved request into a pull request.
Use token rotation
Slack can issue short-lived bot tokens instead of one that never expires. Turn
on token rotation in the app's OAuth & Permissions, then store the app as
an oauth2 credential with its client ID, client secret, and refresh token:
printf '{"client_secret": "%s", "refresh_token": "%s"}' "$SECRET" "$REFRESH" |
oci vault put slack/bot-token --workspace-id WORKSPACE_ID \
--provider slack --credential-type oauth2 \
--client-id "$CLIENT_ID" --grant-type refresh_token \
--secrets-json-stdin --workflow-id WORKFLOW_IDSlack does not let an app turn token rotation off again, so decide before you enable it.
The workflow file does not change. Each run exchanges the refresh token for a 12-hour bot token, and when Slack issues a new refresh token, OutcomeCI saves it back to the Vault before the run continues.
Troubleshooting
Slack will not verify the request URL. The signing secret has to be in the
Vault at the path the trigger names, slack/signing-secret here, and granted
to this workflow. Check both, then retry.
Mentions do nothing. Make sure the app is in the channel, and that the message mentions the app at the top level rather than in a thread reply.
Direct messages do nothing. Confirm messages_tab_enabled is on, the
message.im event is subscribed, and dm is in the trigger's events.
The reply step fails with a credential error. The bot token must be at
slack/bot-token and granted to the workflow. A token from a different
Slack app cannot post as this one.
For every option the receiver and connector support, see Webhooks and Connectors.