Guides

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, or brew install outcomeci/tap/outcomeci-cli. Sign in with oci 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 --create

Sync 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_ID

Otherwise, 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_ID

Signing 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_ID

Piping 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 dm

In 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_ID

Slack 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.