Skip to main content

Slack Onboarding: Connect a Workspace End to End

This guide walks you from a new Slack App at api.slack.com to a working Slack channel in Orbit: OAuth v2 install, signed inbound delivery, webhook routing, and a pinned notifications channel. It complements the Slack channel page, which covers the operator-level configuration and the full error catalog — this guide is the ordered path you follow the first time, and re-run whenever you install or re-verify the App.
This is the tenant-facing Slack App — the one your workspace installs to receive Events API, slash commands, and interactivity payloads. It is not the internal ops-alert webhook that posts Orbit’s own operational alerts to a Devotel-internal channel; that path is not part of your setup.

1. What the Slack App does

There are three inbound surfaces and one outbound surface, all tied to the same Slack App:
  • Events API — Slack POSTs workspace events (app_mention, message, message.channels, message.im, app_home_opened, team_join, reaction_added, link_shared) to Orbit’s inbound endpoint. Every event is persisted and re-ACK’d safely on retry (duplicate: true).
  • Slash commands — the /orbit ... command returns an immediate ephemeral response and fans out the payload as a tenant webhook.
  • Interactivity — Block Kit buttons, shortcuts, and view_submission payloads follow the same signed, persisted, fanned-out path.
  • Outbound notifications — Orbit posts tenant-side events (new conversation, campaign completed, SLA breach, voice-queue SLA warning, and similar notifications) to the channel you pin (see section 6).
All inbound requests are authenticated with the App’s signing secret before anything is dispatched — see section 4.

2. Create the Slack App at api.slack.com

  1. Sign your workspace in to api.slack.com/apps and create an app with Create an app → From scratch.
  2. On the app’s Basic Information page, copy the Client ID and Client Secret. You need these on the Orbit operator; see the Slack channel page — operator setup.
  3. Go to OAuth & Permissions → Redirect URLs and add:
  4. On the same page, request the bot scopes your Slack App will ask for at install. The baseline set Orbit requests by default is:
    If your operator has overridden the default scope list for your deployment, use that exact list; otherwise the baseline above covers install, inbound events, slash commands, interactivity, and posting.

3. Run the OAuth v2 install

The install is browser-driven and fully round-trips before anything is stored:
  1. From the Orbit dashboard, open Channels → Slack and click Connect Workspace.
  2. Orbit mints an HMAC-signed state token (10-minute TTL, nonce-based, one use) and redirects your browser to Slack’s approve screen.
  3. Approve the scopes Slack asks for. Slack redirects back to the callback URL above.
  4. Orbit verifies the signed state, resolves your tenant from it, redeems the temporary code, and stores the workspace credentials in your tenant schema.
A successful install returns the workspace identity:
If the state is tampered or older than 10 minutes, the callback returns 401 INVALID_STATE — restart the install from the dashboard. A 503 SLACK_NOT_CONFIGURED means the operator credentials are not set on the deployment; see the Slack channel page — operator setup and retry.
The callback is intentionally public (no session cookie). Do not close the browser redirect flow early or re-run /callback by hand — trust is anchored to the signed state round-trip, and re-playing the callback a second time returns the same 401 INVALID_STATE.

4. Verify the signing secret

Orbit verifies every inbound Events API, slash-command, and interactivity request using the App’s signing secret. Point your Slack App’s Event Subscriptions, Slash Commands, and Interactivity Request URLs at either of the equivalent inbound endpoints:
Verification follows Slack’s documented spec: X-Slack-Signature: v0=<HMAC-SHA256(secret, "v0:" + timestamp + ":" + rawBody)> with a 5-minute timestamp window. The behavior is fail-closed — anything that cannot be authenticated is rejected, and an unchecked signing secret disables the channel entirely rather than accept unsigned events: Avoid the 503 branch by confirming the operator credentials are set before enabling the channel — a deployment that leaves the signing secret unset will dispatch 503 to Slack and eventually have the subscription disabled by Slack after repeated non-200s. Test the channel with a real Slack delivery: slash commands and Events API retries are the fastest to eyeball. A forged signature or a replayed timestamp returns 401 INVALID_SIGNATURE, which is the correct outcome — do not “fix” this in the receiver.

5. Route inbound events to webhooks

Events fan out as three webhook event types — subscribe the ones your integration needs and keep one handler for all three: Two routing notes:
  • Events dedupe — every Events API delivery is keyed on Slack’s event_id. A retry within the dedupe window returns 200 with duplicate: true, so your webhook handler should treat slack.event_received as at-most-once per event_id.
  • Slash commands ack immediately — the ephemeral reply to the user is returned synchronously; the slack.slash_command webhook fan-out happens asynchronously, so a slow subscriber never holds the Slack ack open.
For a worked inbound example and the exact verifier math, see the Slack channel page — inbound request verification.

6. Pin the post channel

Outbound notifications (new conversations, campaign completion, SLA breaches, voice-queue SLA warnings, and similar) post to the channel you pin. Until you pin one, the publishers resolve to no_channel and drop the notification silently — the fail-open behavior is deliberate so a Slack misconfiguration never blocks the action behind it. Pick a channel from the workspace and pin it:
A successful pin returns the pinned channel and the active connection:
The bot must be a member of the pinned channel, or you must have granted chat:write.public (in the default scope set) to post to a public channel without a /invite. Otherwise Slack returns channel_not_found and the post fails. A pinned id that is not a public or private channel (a DM, or a name instead of an id) returns 400 INVALID_SLACK_CHANNEL_ID.

7. Troubleshooting

Match the symptom to its cause before touching the App: For anything not covered above — scopes, per-event-type handling, or the full operator variable set — see the Slack channel page.