Slack apps for preview environments

Use this runbook to create or repair the Slack app for an OS preview slot. For a batch, prefer Slack's App Manifest API; use the browser for authorization, installation, and recovery from an existing app.

For the end-to-end testing flow and the Niterate (CI bot) duplicate-reply caveat in the Iterate Slack workspace, see docs/slack-testing.md. For the full fleet-expansion sequence, see docs/adding-preview-slots.md.

Each preview slot gets its own Slack app:

OS config Slack label Dashboard URL Slack app name
preview_N preview-N https://os.iterate-preview-N.com iterate (preview-N)
preview_1 preview-1 example https://os.iterate-preview-1.com iterate (preview-1)

Do not reuse the production Slack app for previews, and do not point one Slack app at multiple preview slots. Slack has one active Events API Request URL per app, while OS stores one signing secret per deployed config.

Safety boundary#

  • Inventory and validate manifests without approval. Before creating apps, confirm the exact slot numbers and workspace ID with a human. Use the Iterate test workspace unless the human explicitly names another one.
  • Confirm the exact Doppler configs and secret name before writing them. APP_CONFIG_INTEGRATIONS__SLACK changes deployed behavior without a git diff.
  • Keep Slack client secrets and signing secrets out of chat, screenshots, logs, shell history, and git. Pipe API responses into Doppler; never ask a human to paste credentials into chat. Use the Signing Secret, not the deprecated verification token.
  • Stop on an existing app name, a different workspace, 2FA, CAPTCHA, or a manifest diff outside the approved slots. Inspect existing state instead of creating a near-duplicate.
  • Install and claim the workspace through OS after the Slack app exists. The Slack dashboard's "Install App to Workspace" button is not a substitute for OS's connect flow because OS must store the bot token and claim the Slack team in its own streams.

Manifest#

Use this manifest for slot N. Replace every N placeholder before pasting. Use hyphen form (preview-N) in Slack labels and hostnames, and underscore form (preview_N) only for Doppler config names.

display_information:
  name: iterate (preview-N)
  description: iterate Slack agent for preview-N testing only
  background_color: "#111827"
features:
  app_home:
    home_tab_enabled: false
    messages_tab_enabled: true
    messages_tab_read_only_enabled: false
  agent_view:
    agent_description: Test iterate's preview-N Slack agent against the preview OS deployment.
    suggested_prompts:
      - title: Debug this thread
        message: "!debug"
  bot_user:
    display_name: iterate-preview-N
    always_online: true
oauth_config:
  redirect_urls:
    - https://os.iterate-preview-N.com/api/integrations/slack/callback
  scopes:
    bot:
      - channels:history
      - channels:join
      - channels:manage
      - channels:read
      - chat:write
      - chat:write.public
      - files:read
      - files:write
      - groups:history
      - groups:read
      - im:history
      - im:read
      - im:write
      - mpim:history
      - mpim:read
      - reactions:read
      - reactions:write
      - users.profile:read
      - users:read
      - users:read.email
      - assistant:write
      - conversations.connect:write
settings:
  event_subscriptions:
    request_url: https://os.iterate-preview-N.com/api/integrations/slack/webhook
    bot_events:
      - app_home_opened
      - message.channels
      - message.groups
      - message.im
      - message.mpim
  interactivity:
    is_enabled: true
    request_url: https://os.iterate-preview-N.com/api/integrations/slack/interactivity-webhook
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false

The bot scopes intentionally match DEFAULT_SLACK_BOT_SCOPES in apps/os/src/config.ts. If the runtime default changes, update this manifest and reinstall any affected Slack apps through the OS connect flow.

features.agent_view is included because newly created Slack AI apps use the Agent messaging experience. OS currently routes real conversation work from Slack message.* events; app_home_opened is included for Slack's agent surface and future diagnostics, but it is not the smoke-test signal by itself.

Preferred creation flow: App Manifest API#

An app configuration token can create and configure any app belonging to its user in one workspace. Generate it under Your App Configuration Tokens on the Slack app settings page. It normally expires after 12 hours; do not store it in git or a shared preview config.

The safe batch flow is:

  1. Render the bootstrap manifest in the browser-fallback section below for every slot.
  2. Call apps.manifest.validate for all of them. Validation is read-only.
  3. Present the workspace ID, app names, validation result, and intended Doppler writes for approval.
  4. Call apps.manifest.create sequentially. It is a Tier 1 method, so respect Retry-After instead of launching ten calls in parallel.
  5. Check ok, the returned app ID, and the OAuth URL. Pipe the returned credentials directly into the matching Doppler config.

For one slot, save the rendered bootstrap manifest in a temporary directory outside the repository, then run:

read -rs 'SLACK_APP_CONFIG_TOKEN?Slack app configuration token: '
export SLACK_APP_CONFIG_TOKEN
printf '\n'
 
response_dir=$(mktemp -d)
chmod 700 "$response_dir"
cleanup() {
  unset SLACK_APP_CONFIG_TOKEN
  rm -f "$response_dir/manifest.yaml" "$response_dir/response.json"
  rmdir "$response_dir"
}
trap cleanup EXIT
 
jq -n --rawfile manifest "$response_dir/manifest.yaml" \
  '{manifest:$manifest}' |
  curl --fail-with-body --silent --show-error \
    https://slack.com/api/apps.manifest.create \
    -H "Authorization: Bearer $SLACK_APP_CONFIG_TOKEN" \
    -H 'Content-Type: application/json' \
    --data-binary @- >"$response_dir/response.json"
 
jq -e '.ok == true and .app_id and .credentials.client_id and
  .credentials.client_secret and .credentials.signing_secret' \
  "$response_dir/response.json" >/dev/null
 
jq -c '{oauthClientId:.credentials.client_id,
  oauthClientSecret:.credentials.client_secret,
  webhookSigningSecret:.credentials.signing_secret}' \
  "$response_dir/response.json" |
  doppler secrets set APP_CONFIG_INTEGRATIONS__SLACK \
    --project os --config preview_N --silent

Do not enable shell tracing. Do not print response.json: it contains secrets. Record the non-secret app ID and slot in the expansion ledger before the trap removes the temporary directory.

After OS is deployed with the signing secret, call apps.manifest.update with the full manifest. Slack can then verify the Events API and interactivity URLs.

Reference: Slack App Manifest API and apps.manifest.create.

Browser fallback#

  1. Open https://api.slack.com/apps?new_app=1.
  2. Choose From a manifest.
  3. Select the Iterate test/development workspace.
  4. Paste the filled manifest for the slot.
  5. Review Slack's summary and create the app.

If Slack refuses the manifest because the Events API Request URL cannot be verified yet, create the app with a bootstrap manifest first:

display_information:
  name: iterate (preview-N)
  description: iterate Slack agent for preview-N testing only
  background_color: "#111827"
features:
  bot_user:
    display_name: iterate-preview-N
    always_online: true
oauth_config:
  redirect_urls:
    - https://os.iterate-preview-N.com/api/integrations/slack/callback
  scopes:
    bot:
      - channels:history
      - channels:join
      - channels:manage
      - channels:read
      - chat:write
      - chat:write.public
      - files:read
      - files:write
      - groups:history
      - groups:read
      - im:history
      - im:read
      - im:write
      - mpim:history
      - mpim:read
      - reactions:read
      - reactions:write
      - users.profile:read
      - users:read
      - users:read.email
      - assistant:write
      - conversations.connect:write
settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false

Then finish Doppler and deployment, return to App Manifest, paste the full manifest, and save it. Slack URL verification will succeed only after the preview worker has the matching signing secret and is deployed.

The bootstrap manifest intentionally omits features.agent_view, app-home settings, Events API subscriptions, and interactivity. Slack validates Agent View against message.im, app_home_opened, and a verifiable request URL, so those fields belong in the full manifest only after the preview worker can answer Slack's URL verification challenge.

Browser credential handoff#

After Slack creates the app, open Basic Information and collect:

  • Client ID
  • Client Secret
  • Signing Secret
  • Optional fallback for smoke testing: Bot User OAuth Token from OAuth & Permissions
  • Optional for inventory only: App ID and Team ID

Do not use the Verification Token. Enter these values into a local secure prompt or write them directly from the page into Doppler. Do not copy them into chat or a task file.

Write Doppler config#

From repo root, set the OS preview config. Read values silently so they do not enter shell history:

read -r 'SLACK_CLIENT_ID?Client ID: '
read -rs 'SLACK_CLIENT_SECRET?Client secret: '; printf '\n'
read -rs 'SLACK_SIGNING_SECRET?Signing secret: '; printf '\n'
read -rs 'SLACK_BOT_TOKEN?Bot token (optional): '; printf '\n'
 
jq -nc \
  --arg id "$SLACK_CLIENT_ID" \
  --arg secret "$SLACK_CLIENT_SECRET" \
  --arg signing "$SLACK_SIGNING_SECRET" \
  --arg bot "$SLACK_BOT_TOKEN" \
  '{oauthClientId:$id,oauthClientSecret:$secret,webhookSigningSecret:$signing}
    + if $bot == "" then {} else {botToken:$bot} end' |
  doppler secrets set APP_CONFIG_INTEGRATIONS__SLACK \
    --project os \
    --config preview_N \
    --silent

Verify shape without printing secret material:

doppler secrets get APP_CONFIG_INTEGRATIONS__SLACK \
  --project os \
  --config preview_N \
  --plain |
  jq -e '.oauthClientId and .oauthClientSecret and .webhookSigningSecret' >/dev/null

Then redeploy OS so the runtime config is baked into the worker:

(cd apps/os && doppler run --project os --config preview_N -- pnpm run deploy)

For a brand-new preview stack, follow the preview creation flow in docs/dev-environments.md instead of directly deploying an unleased slot.

Enable and verify Slack delivery#

  1. Return to the Slack app in the browser.
  2. Open App Manifest and save the full manifest from this document.
  3. Open Event Subscriptions and confirm the Request URL is verified: https://os.iterate-preview-N.com/api/integrations/slack/webhook.
  4. Open Interactivity & Shortcuts and confirm the request URL is: https://os.iterate-preview-N.com/api/integrations/slack/interactivity-webhook.
  5. If Slack says event delivery has been disabled, re-enable it only after the Request URL verifies. A bad signing secret or stale deploy can otherwise push the app back over Slack's failure threshold.

OS verifies Slack signatures before answering url_verification, so a 401 during URL verification almost always means the Slack app's Signing Secret and the deployed APP_CONFIG_INTEGRATIONS__SLACK.webhookSigningSecret do not match, or OS was not redeployed after the Doppler update.

Claim the workspace through OS#

  1. Open the preview dashboard: https://os.iterate-preview-N.com.
  2. Sign in with a real browser session or an agent-authenticated session. See apps/os/docs/preview-browser-smoke.md for the headless browser path.
  3. Create or open the project that should receive Slack events.
  4. Go to the project Integrations page and click Connect Slack.
  5. Approve the Slack OAuth screen.
  6. Confirm the Integrations page shows Slack connected to the expected workspace.

This writes the workspace bot token into the project secret /secrets/integrations/slack/<connection>/bot-token, appends events.iterate.com/slack/connected on /integrations/slack/<connection>, and claims the Slack team in the preview deployment's /integrations/_directory stream.

If OS returns slack_team_already_claimed, that workspace is already claimed by a different project in the same preview deployment. Disconnect Slack from the old project or use another workspace. The same Slack workspace may still be claimed independently in a different preview slot because each slot is a separate deployment.

Smoke test#

Use a private or dedicated Slack test channel; message events in channels where the bot is present are real triggers. In the Iterate Slack workspace, avoid shared public channels for preview testing unless duplicate replies from the production iterate app or legacy Niterate (CI bot) actor are acceptable.

  1. Invite the preview bot to the channel:

    /invite @iterate-preview-N
  2. Send a normal root message or thread reply. OS should add an eyes reaction after routing the webhook.

  3. For a deterministic visible Web API call, send:

    !debug

    The Slack processor compiles this into a call to itx.integrations.slack.get("<connection>").chat.postMessage in the same thread.

  4. In OS, inspect the project stream /projects/<projectSlug>/streams/integrations/slack. A successful real webhook path includes events.iterate.com/slack/webhook-received and, for routable messages, events.iterate.com/slack/thread-route-configured.

The synthetic signed-webhook e2e in apps/os/e2e/vitest/slack-agent.e2e.test.ts only needs the preview signing secret. This Slack app setup is required for a real Slack round trip with Slack's Events API and OAuth.

Troubleshooting#

  • Slack cannot verify the Request URL: confirm the full manifest URL uses the right preview-N, the deployed worker is reachable, the Doppler JSON has the current Signing Secret, and OS was redeployed after the Doppler change.
  • OAuth callback fails: confirm the redirect URL in Slack is exactly https://os.iterate-preview-N.com/api/integrations/slack/callback and that Doppler has the matching Client ID and Client Secret.
  • Events verify but no project reacts: the Slack workspace may not be claimed through OS. Run the OS Connect Slack flow for the intended project.
  • Preview and another Iterate-owned bot both reply: this is usually an internal Iterate Slack workspace artifact. Production and preview apps can both be installed there with broad message.channels subscriptions, and chat:write.public means an app can post into public channels even when its bot user is not visibly in the channel. See docs/slack-testing.md.
  • Channel messages do nothing: invite the preview bot to the channel and verify the app has been reinstalled after any scope changes. Slack applies manifest subscription changes immediately, but events gated by new scopes do not fire for old installs until reinstall.
  • App delivery was auto-disabled: fix the URL/signing secret first, then re-enable delivery in Slack. Slack temporarily disables event subscriptions when almost all deliveries fail over a rolling window.

Slack references#

Was this page helpful?