Every Slack Integration Starts Outside Salesforce
Every Slack to Salesforce integration starts in the same place, and it is not Salesforce. Before an Apex class or a Flow can post a single message, Slack needs to know exactly who is calling its Web API and what that caller is allowed to touch. That decision is made at api.slack.com/apps, in a console most Salesforce admins have never opened, and every choice there, scopes, token type, rotation, either sets up a clean install or schedules a debugging session for three weeks later.
Salesforce ships first-party Slack integrations, Salesforce channels, Agentforce in Slack, and Sales Cloud for Slack, and they cover record-centric collaboration well. A custom Slack app covers what they do not: bespoke notifications from Apex or Flow, lookups by a Contact's email, approval pings. Cloudsheer has shipped this exact pattern across 19 client projects, and the credential setup below decides how the rest of the build goes.
This article is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/slack/technical-guide: create the app, add bot token scopes, install it, wire the token into Salesforce, and prove it works with a smoke test.
What You Need Before You Start
- A Slack workspace where you can install apps. If 'Require approved apps' is on, installs become requests a Slack admin approves from the Requests to install tab, so find that admin first.
- A Salesforce org, sandbox or production, where you can create Named Credentials and permission sets. External Credential secrets do not survive a sandbox refresh, so plan to re-enter the token per org.
- A password manager or secrets vault. The xoxb- token behaves like a production password, and the Salesforce field you paste it into is write-only after save.
- The channel ID of a test channel, the C0123456789 style value from the channel's details pane, not the #name.
- About 30 minutes. No code until the final smoke test, which is ten lines of Anonymous Apex.
Step 1: Create the Slack App From Scratch
- Go to https://api.slack.com/apps and sign in to the workspace you will develop in.
- Click Create New App (Slack's docs call it 'Create an app'; the dashboard renders 'Create New App').
- In the modal choose From scratch. The other option, From a manifest, recreates an app from a YAML file, wrong place to start.
- Enter an App Name your users will recognize, something like Salesforce Notifier, pick your workspace under 'Pick a workspace to develop your app in', and click Create App.
There is no sandbox versus production split on the Slack side: one app installs into a dev workspace and a production workspace, each install minting its own token. On the Salesforce side you enter the matching token into each org.
Step 2: Add Bot Token Scopes Under OAuth and Permissions
In the left sidebar click OAuth & Permissions and scroll to the Scopes section. There are two lists, Bot Token Scopes and User Token Scopes. A bot token is the app's own identity and survives the installing person leaving; a user token acts as that human and dies with their deactivation. Slack's quickstart says it plainly: 'In general, you'll add scopes to your bot token, not your user token.' Leave User Token Scopes empty.
- Under Bot Token Scopes click Add an OAuth Scope and add chat:write. This lets the app post via chat.postMessage in channels it has been invited to.
- Add channels:read to list public channels through conversations.list. Private channels need groups:read instead.
- Add users:read, then users:read.email. Slack requires them to be requested together, and since January 4, 2017 no email is ever returned without users:read.email. This pair powers users.lookupByEmail, which turns a Salesforce Contact email into a Slack user ID.
- Optionally add chat:write.public to post into any public channel without an invite; without it, someone must /invite the bot into every target channel.
Step 3: Install to Workspace and Copy the xoxb- Token
- Scroll to the top of OAuth & Permissions and click Install to Workspace (also reachable from the Install App sidebar item).
- Review the consent screen listing the scopes you added and click Allow. In approval-required workspaces this becomes a request to install; the admin sees the full scope list first.
- Back on OAuth & Permissions, copy the Bot User OAuth Token from the OAuth Tokens section. It starts with xoxb-. Copy it into your password manager immediately and treat it like a production password from this second onward: no email, no shared doc, no spreadsheet.
- An xoxp- token here means you requested User Token Scopes; for a system integration remove them and reinstall.
By default this token is long-lived: it does not expire on a timer, it dies only if the app is uninstalled or the token revoked or regenerated. This page also offers a token rotation opt-in. Do not click it. Rotation is irreversible ('Token rotation may not be turned off once it's turned on'), tokens then expire every 12 hours, and a static Named Credential header would break twice a day unless you build a scheduled refresh job.
Step 4: Note the App Credentials You Might Need Later
Click Basic Information in the sidebar and open App Credentials: Client ID, Client Secret, Signing Secret, and a deprecated Verification Token. This pattern needs neither of the first two; they matter only for redirect-based OAuth installs and rotation refresh calls. Copy the Signing Secret (click Show) only if you will receive inbound Slack events, since it is the HMAC-SHA256 key for verifying X-Slack-Signature headers. Ignore the Verification Token; Slack says 'We strongly recommend you only use the Signing Secret from now on.' Skip Redirect URLs too: a single-workspace app installed from the console never touches the redirect flow, so no Salesforce Auth Provider is needed.
Step 5: Create the External Credential in Salesforce
- In Setup, type Named Credentials into Quick Find (Setup > Security > Named Credentials), open the External Credentials tab, and click New. Label: Slack API. Name: Slack_API. Authentication Protocol: Custom. Save.
- On the External Credential detail page, under Principals click New. Parameter Name: SlackBot. Sequence: 1. Save.
- Add an Authentication Parameter to that principal: Name BotToken, Value your xoxb- token. Paste it clean; a trailing newline or space is the top cause of invalid_auth later. The value is encrypted and cannot be viewed after save; the only readable copy lives back on Slack's OAuth & Permissions page.
- Under Custom Headers click New. Name: Authorization. Value: {!'Bearer ' & $Credential.Slack_API.BotToken}. The syntax is $Credential.<ExternalCredentialDeveloperName>.<ParameterName>, and the space after Bearer is mandatory.
Step 6: Create the Named Credential and Grant Principal Access
- Switch to the Named Credentials tab and click New. Label: Slack. Name: Slack. URL: https://slack.com/api. External Credential: Slack_API. Check Enabled for Callouts.
- Under Callout Options check Allow Formulas in HTTP Header. Skip this and Salesforce sends Slack the literal text {!'Bearer ' & ...} instead of evaluating it, and Slack answers with invalid_auth.
- Save. No Remote Site Setting is needed; Named Credentials bypass that mechanism entirely.
- Create or edit a permission set, open External Credential Principal Access, click Edit, move Slack_API - SlackBot to Enabled, and save. Assign it to every user who will run the callout, yourself included. Even a System Administrator fails without explicit principal access.
Step 7: Invite the Bot and Run the Smoke Test
- In Slack, open your test channel and run /invite @YourApp. chat:write alone cannot post into a channel the bot is not a member of; skipping this earns you a not_in_channel error.
- In Salesforce, open Execute Anonymous in Developer Console. Build an HttpRequest with endpoint callout:Slack/chat.postMessage, method POST, header Content-Type set to application/json; charset=utf-8, and body {"channel":"C0123456789","text":"Hello from Salesforce"} using your real channel ID. Send it with new Http().send(req).
- A healthy response is HTTP 200 with ok true and a message timestamp. Slack returns 200 even for failures, so check the ok field; when false, the error field maps to the list below.
- Mind the rate limit: chat.postMessage allows roughly one message per second per channel with short bursts. Post from a Queueable with backoff, never from a loop over records.
Common Errors and How to Fix Them
- missing_scope: the token lacks a required scope. Add it under Bot Token Scopes and reinstall the app, scope changes take effect only after reinstall, and confirm you sent the xoxb- token, not an xoxp- one.
- not_in_channel: the bot is not a member of the target channel. Run /invite @YourApp there, or add chat:write.public (public channels only) and reinstall.
- channel_not_found: you passed #general or a stale ID. Use the C0123456789 channel ID; IDs are workspace-specific, and for private channels the bot must already be a member.
- invalid_auth: the token reached Slack malformed. Check for whitespace in the External Credential parameter, a missing 'Bearer ' prefix in the formula, or Allow Formulas in HTTP Header left unchecked so the literal {!...} text was sent.
- not_authed: no Authorization header arrived. Verify the custom header exists on the External Credential and the running user has principal access through a permission set.
- token_revoked or token_expired: the app was uninstalled, credentials regenerated, or a rotation-enabled token passed its 12 hour life. Reinstall and copy the fresh xoxb- into the principal.
- rate_limited with HTTP 429: you exceeded roughly one message per second per channel. Honor the Retry-After header and queue posts with backoff in Salesforce.
- A Salesforce callout exception about being unable to access the credentials: the running user lacks External Credential Principal Access for Slack_API - SlackBot. Assign the permission set and retry.
Security Checklist Before You Ship
- The xoxb- token lives in exactly two places: the External Credential principal, which is write-only, and your secrets vault. Nowhere else, ever.
- User Token Scopes are empty. If an xoxp- token exists, someone added user scopes; remove them and reinstall.
- Token rotation stays off unless you built a refresh job. The opt-in is irreversible and a 12 hour expiry breaks the static header.
- Scopes are minimal. Every extra scope widens the blast radius, forces a reinstall, and can trigger a new admin approval round.
- Any inbound endpoint verifies requests with the Signing Secret: HMAC-SHA256 over v0:timestamp:body, constant-time compare with X-Slack-Signature, rejecting timestamps over five minutes old. The deprecated Verification Token proves nothing.
- The permission set granting principal access goes only to the integration user and named admins, never a broad profile.
- After every sandbox refresh, re-enter the token; External Credential secrets do not survive the copy.
Where This Fits in the Full Slack Build
What you just built is the credential spine: a scoped Slack app, a long-lived bot token, and a Named Credential that turns any Apex or Flow callout into callout:Slack/<method>. A production build layers on a reusable Apex service class, Flow-invocable actions, users.lookupByEmail to map Salesforce people to Slack IDs, Event Subscriptions with a signing-secret-verified endpoint, and queueing that respects the rate limit. We walk that full architecture, with code, in the technical guide at cloudsheer.com/integrations/slack/technical-guide.
If you build with AI tooling, we packaged this playbook as a free Claude Code skill. Run npx skills add shivamgoel-cloudsheer/Claude-skills --skill slack-salesforce and Claude Code picks up our scope tables, wiring steps, and error map.
How Cloudsheer Can Help
Cloudsheer is a Salesforce consultancy that has delivered this Slack pattern across 19 client projects, from single-channel deal alerts to two-way approval flows with signed inbound events. We know the sharp edges because we have hit every one: the reinstall-after-scope-change loop, the admin approval wall, the sandbox refresh that blanks the token, the rotation switch you cannot unclick.
If you want this integration built, hardened, or rescued, we can scope it in one conversation, including which parts the official Salesforce channels and Agentforce in Slack features already cover so you do not build what you could configure. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
