Slack token types at a glance
Slack bot tokens start with xoxb- and act as your app's bot user; user tokens start with xoxp- and act as the specific person who authorized the app; app-level tokens start with xapp- and represent the app itself, which is what Socket Mode needs (with the connections:write scope). Most bots need an xoxb- token for the Web API and, only if they use Socket Mode, an xapp- token to open the WebSocket.
Last verified: November 2026 against Slack's token types documentation, token rotation guide and Socket Mode guide.
Slack documents six current token types. The prefix tells you which one you are holding:
| Token | Prefix | Acts as | Expires | Typical use |
|---|---|---|---|---|
| Bot token | xoxb- | The app's bot user, independent of any person | Never, unless rotation is on (then 12 h) | Posting messages, reading channels the bot is in, almost all bot work |
| User token | xoxp- | The human who authorized the app, with their visibility | Never, unless rotation is on (then 12 h) | Acting on behalf of a user, user-only methods such as search.messages |
| App-level token | xapp- | The app across every organization it is installed in | Never by default; opt-in rotation (12 h, xoxe.xapp-) | Socket Mode, listing event authorizations |
| Configuration token | xoxe.xoxp- (refresh xoxe-) | A user in one workspace, for any of their apps | 12 hours | App Manifest APIs: creating and updating apps from code |
| Workflow token | xwfp- | A subset of the bot token, for one workflow step | 15 minutes or when the step finishes | Workflow function steps; cannot perform actions that need a user scope |
| Service token | Not specified | A long-lived user token | Never; cannot be rotated | Slack CLI automation for Deno Slack SDK apps only |
The rest of this guide covers which of these your code actually needs, where each one lives in the app settings, how scopes attach to them, how rotation changes their shape, and what each auth error means.
xoxb vs xoxp: bot token or user token?
Slack's own advice is that a bot token "is usually for the best", and the reason is lifecycle. A bot token is tied to the app, not to a person, so the integration keeps working when the employee who installed it leaves and their account is deactivated. A user token is tied to that person: it sees exactly the channels, DMs and files they can see, its write actions appear as that person, and deactivating the person revokes it.
| Question | Bot token (xoxb-) | User token (xoxp-) |
|---|---|---|
| Who appears as the author | The app's bot user | The authorizing human |
| What it can read | Conversations the bot is a member of, within its scopes | Everything the user can see, within its scopes |
| Survives the installer leaving | Yes | No |
| Where scopes are declared | Bot Token Scopes in OAuth & Permissions | User Token Scopes in OAuth & Permissions |
In the oauth.v2.access response | access_token with token_type: "bot" | authed_user.access_token with token_type: "user" |
Choose a user token only when the action is inherently personal: setting someone's status, posting as them, or calling a method that has no bot equivalent. Slack's method reference lists the accepted token types for each method under Scopes. Some representative cases:
| Method | Token types accepted | Scope |
|---|---|---|
chat.postMessage | Bot or user | chat:write |
search.messages | User only | search:read (user scope) |
apps.connections.open | App-level only | connections:write |
apps.event.authorizations.list | App-level only | authorizations:read |
apps.manifest.create | Configuration token | app_configurations:write |
auth.test | Any | None required |
Calling a method with a token type it does not accept returns not_allowed_token_type, which is a different problem from a missing scope and is not fixed by adding scopes. The classic example is calling search.messages with a bot token.
Where to find each token in your app settings
All of these live at api.slack.com/apps under your app. The bot and user tokens only exist after the app is installed to a workspace.
| You need | Where it is |
|---|---|
Bot token (xoxb-) | OAuth & Permissions, add at least one Bot Token Scope, click Install to Workspace, then copy it from OAuth Tokens on the same page. For apps installed by other workspaces, it arrives in your oauth.v2.access response instead. |
User token (xoxp-) | Same OAuth Tokens section, only if you requested User Token Scopes. Other users' tokens come from OAuth under authed_user. |
App-level token (xapp-) | Basic Information, scroll to App-Level Tokens and use the generate button; add connections:write for Socket Mode. Enabling Socket Mode also prompts you to create one. |
| Configuration token | The app list page (not inside an app): Your App Configuration Tokens, Generate Token. One per user and workspace, valid for all your apps there. |
| Client ID / client secret | Basic Information, App Credentials. Used to exchange OAuth codes and refresh tokens; not a token itself. |
| Signing secret | Basic Information, App Credentials. Used to verify that incoming HTTP requests came from Slack; it replaces the deprecated verification token. |
The signing secret is the one most often pasted into the wrong field. It never goes in an Authorization header; it is only used to compute an HMAC over incoming request bodies. If your bot rejects every event with a signature error, check that the signing secret and not a token is configured for verification.
Once you have a token, confirm what it is rather than trusting the prefix you think you copied:
curl -s -X POST https://slack.com/api/auth.test \
-H "Authorization: Bearer xoxb-..."A bot token's response includes a bot_id; a user token's response does not. An invalid token returns invalid_auth, and a missing one returns not_authed. Every Web API response also carries an x-oauth-scopes header listing the scopes the calling token has, which settles most "which scopes does this token really have" debates in one request.
Which scopes go on which token
A scope is not global to your app; it is granted to a specific token type. In OAuth & Permissions you declare Bot Token Scopes and User Token Scopes separately, and each scope's reference page states its supported token types. Three behaviors from Slack's OAuth guide explain most scope confusion:
- Scopes are additive. Re-running OAuth with new scopes adds them to the existing token's set. There is no way to remove a scope from a token except revoking it entirely.
- Bot and user scope sets are independent. Granting
channels:historyas a user scope does nothing for yourxoxb-token, and calls made with the bot token still fail withmissing_scope. - App-level scopes are their own family.
connections:writeandauthorizations:readonly work onxapp-tokens. Their reference pages list app-level as the only supported token type, and ordinary bot or user scopes do not apply to an app-level token.
When a call fails with missing_scope, the error response typically names the needed scope and what the token provided. Add it to the right token family, then reinstall the app so a token carrying the new scope is issued; the reinstall step is covered in detail in our Slack bot troubleshooting guide. Request only what you use: Slack's security guidance is least privilege, and its OAuth guide states that apps must demonstrate usage of every scope they request.
The xapp token and Socket Mode
Socket Mode lets your app receive Events API and interactivity payloads over a WebSocket instead of a public HTTPS Request URL, which is why it is popular behind corporate firewalls. It needs two tokens, and missing either one is a common setup failure:
- App-level token (
xapp-) withconnections:write, used only to callapps.connections.open, which returns a temporarywss://URL. Slack's reference specifies that this token must be sent in the HTTPAuthorizationheader; sending it as a POST parameter results in an error. - Bot token (
xoxb-), used for everything the bot does once events arrive:chat.postMessage,conversations.historyand so on.
export SLACK_APP_TOKEN='xapp-...' # opens the socket
export SLACK_BOT_TOKEN='xoxb-...' # calls the Web API
curl -s -X POST https://slack.com/api/apps.connections.open \
-H "Authorization: Bearer $SLACK_APP_TOKEN"
# {"ok": true, "url": "wss://..."}Bolt for JavaScript, Python and Java handle the connection and its regular refresh for you; you pass both values (appToken and token in Bolt for JavaScript). Two constraints from Slack's docs to know before you commit to it: Socket Mode is only available to apps using granular permissions, and Socket Mode apps are not currently allowed in the public Slack Marketplace. If you plan to distribute publicly, build for HTTP Request URLs.
Swapping the tokens produces the most confusing errors in this area. Passing an xoxb- token to apps.connections.open, or an xapp- token to chat.postMessage, both return not_allowed_token_type.
Token rotation: xoxe tokens, refresh tokens and the 12-hour clock
By default, bot and user OAuth tokens never expire. Token rotation, enabled per app in OAuth & Permissions (or under Published App Settings for Marketplace apps), replaces them with access tokens that expire every 12 hours plus single-use refresh tokens. Once enabled, rotation cannot be turned off, so test in a development app first.
| Item | With rotation enabled |
|---|---|
| Bot access token | xoxe.xoxb-... |
| User access token | xoxe.xoxp-... |
| Refresh token | xoxe-1-..., single use |
| Lifetime | expires_in: 43200 seconds (12 hours), always |
| Refresh call | oauth.v2.access with grant_type=refresh_token, client_id, client_secret |
| Active tokens | At most 2; refreshing again revokes the oldest extra token |
| Manifest setting | settings.token_rotation_enabled: true |
curl -s -X POST https://slack.com/api/oauth.v2.access \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET \
-d grant_type=refresh_token \
-d refresh_token=xoxe-1-...The rules that bite in production:
- Persist the new refresh token every time. Each refresh returns a new refresh token, and the one you used is revoked after a short grace period. Code that keeps reusing the original refresh token works once and then fails with
invalid_refresh_token. - Refresh on a schedule, before expiry. An expired access token returns
token_expired. With several workers, centralize refresh in one place, or the two-active-token limit will revoke tokens other workers are still using. - Migrating an existing app means calling
oauth.v2.exchangeonce per long-lived token. Each token can only be exchanged once, and Slack warns not toauth.revokethe old long-lived token yourself, or the user must reinstall; it is expired for you after the first refresh. - Bolt handles it automatically; Slack lists Bolt for JavaScript 3.5, Bolt for Python 1.7 and Bolt for Java 1.9 as the versions with rotation support.
Rotating app-level and configuration tokens
App-level tokens can opt into rotation separately, from the Token rotation control in the App-Level Tokens section or with settings.app_level_token_rotation_enabled in the manifest (granular bot apps only; workflow apps are not eligible). New ones then start with xoxe.xapp-, work anywhere xapp- tokens do including apps.connections.open, expire after 12 hours, and refresh through the same oauth.v2.access call, returning token_type: "app_level". Up to two can be live at once so you can refresh without dropping the socket. The access and refresh values are shown only once at creation; lose them and you must create a new token. Existing app-level tokens created before you enable rotation keep working unchanged.
Configuration tokens always rotate: they expire 12 hours after generation and are refreshed with tooling.tokens.rotate, passing the xoxe- refresh token issued alongside them.
Token errors and what each one means
Slack Web API failures come back as "ok": false with an error string, usually with HTTP 200, so check ok rather than relying on the status code. The token-related errors, with Slack's own definitions:
| Error | Slack's definition | Usual fix |
|---|---|---|
not_authed | No authentication token provided | The header is missing or misspelled, or the env var is empty at runtime |
invalid_auth | Some aspect of authentication cannot be validated: the token is invalid, or the request comes from an IP address not on the app's allowed list | Check for truncation, whitespace or the wrong workspace's token; check IP allowlisting |
not_allowed_token_type | The token type used in this request is not allowed | Use the token type the method accepts (bot vs user vs app-level) |
missing_scope | The token is not granted the specific scope required | Add the scope to the correct token family and reinstall |
token_revoked | Token is for a deleted user or workspace, or the app has been removed (user token) | Reinstall or re-authorize; the old token cannot be revived |
token_expired | Authentication token has expired | Rotation is on and the refresh did not happen in time |
account_inactive | Token is for a deleted user or workspace (bot token) | The workspace or bot user is gone; reinstall if the workspace still exists |
invalid_refresh_token | The given refresh token is invalid | You reused a spent refresh token; restart from the latest stored one or re-run OAuth |
The fastest triage is still auth.test with the exact value your code loads at runtime. If it succeeds, the token is valid and the problem is token type or scope; if it fails, nothing else you check matters until it passes. The full list of Slack API error strings, with causes and fixes, is in our Slack error reference, and rate limits by method tier are on the Slack limits page.
Revoking tokens and keeping them safe
auth.revoke revokes the token you call it with; pass test=1 to check behavior without actually revoking. Its effect depends on the token and on rotation:
- Revoking a bot token does not uninstall the app, but it deactivates the bot user and removes its channel memberships. Incoming webhooks associated with the bot token are removed, and so are its slash commands unless a user token for the same app carries the
commandsscope. - With rotation enabled,
auth.revokerevokes only that single token; the installation, event delivery and the ability to mint new tokens remain. apps.uninstallremoves the app and revokes every token for that installation.
Slack's security best practices add a few rules worth adopting as policy:
- Never hardcode tokens or the client secret; use a secrets manager in production and inject them at runtime.
- Send tokens in the
Authorization: Bearerheader or a POST body, never in a GET query string, where they end up in logs. - Never echo tokens back to users, including in error messages, and store each one linked to its workspace and user so one tenant's token can never be used for another.
- Restrict token use by IP under OAuth & Permissions, Restrict API Token Usage: up to 20 IPv4 addresses or CIDR ranges. Calls from anywhere else fail with
invalid_auth, which is worth remembering when a new server suddenly cannot authenticate. - Turn on token rotation for anything distributed, so a leaked access token is useful for at most 12 hours.
Two legacy notes. User tokens created before August 2016 may have 6- or 10-character secrets; Slack offers a one-time rotation to a longer secret with oauth.v2.beginShortTokenRotation and oauth.v2.completeShortTokenRotation. And legacy bot tokens, workspace-app tokens (fully deprecated in August 2021) and legacy custom-integration tokens are no longer recommended; newer platform features are not supported on legacy bot tokens, so migrate rather than debug them.
If the goal is a working support or FAQ bot in Slack rather than owning OAuth and rotation code, Conferbot's Slack integration lets you build the conversation in a visual editor. Either way, the token model above is what runs underneath.
Was this article helpful?
Build and deploy in 10 minutes. No coding needed.
Slack Token Types Explained FAQ
Everything you need to know about chatbots for slack token types explained.
About the Author
The Conferbot team writes about building, deploying, and improving AI chatbots.
View all articlesRelated Articles
From the reference shelf
Fact-checked reference pages and free tools for the platform this article covers.