How to Connect ChatGPT to Slack (with OAuth)
๐ค AI Agent? You can point your AI agent at this article and it can follow the steps to connect automatically. Everything it needs is here: the API endpoints, request/response formats, scopes, and working code samples. The TapAuth API is two calls โ
POST https://tapauth.ai/api/v1/grantsto create a grant, thenGET https://tapauth.ai/api/v1/token/{id}(with Bearer auth) to retrieve the access token. No API key needed โ your agent can follow the code examples below end-to-end.
ChatGPT can reason through complex problems, generate code, and analyze data โ but the moment you want it to post a message in your team's Slack channel or read a thread for context, it hits a wall. It needs a Slack OAuth token, and Slack's OAuth implementation is one of the most non-standard in the industry.
This is exactly the kind of problem TapAuth was built to solve. TapAuth is the token layer for AI agents โ one API call, and your agent gets a valid, scoped, user-approved Slack token. No OAuth implementation. No redirect servers. No wrestling with Slack's quirks. Your agent asks for a token and gets one.
This guide walks through the full manual Slack OAuth flow (so you understand what you're skipping) and then the TapAuth approach. Spoiler: Slack is one of the worst providers to implement manually.
Why Slack OAuth is especially painful for agents
Every OAuth provider has quirks. Slack has a collection of them that make agent integration particularly frustrating:
- Non-standard token responses. Most OAuth providers return
access_tokenat the top level of the JSON response. Slack nests it insideauthed_user.access_tokenfor user tokens and puts bot tokens at the top level. If your code expects the standard format, you'll get a token that belongs to the wrong identity โ or no token at all. - No refresh tokens (for most token types). Slack's user tokens don't expire, which sounds nice until you realize it means there's no built-in rotation. A leaked token works forever unless the user explicitly revokes it. Bot tokens added rotation in 2020, but user tokens are still permanent โ a security model from a different era.
- Granular but confusing scopes. Slack has separate scope namespaces for bot tokens and user tokens.
channels:readis a bot scope.channels:readas a user scope is different. You need to passuser_scopeseparately fromscopein the authorization URL, and mixing them up produces silent failures. - Workspace-scoped tokens. Unlike GitHub or Google where tokens work across an account, Slack tokens are scoped to a single workspace. If your user is in five workspaces, you need five separate OAuth grants. Your agent needs to know which workspace it's operating in.
- No standard error format. Slack returns
{"ok": false, "error": "invalid_auth"}instead of standard OAuth error responses. Your error handling code from other providers won't work here โ you need Slack-specific parsing.
These aren't edge cases. They're the default experience of implementing Slack OAuth. For an AI agent that just wants to post a message, you're signing up for hours of debugging non-standard behavior.
What the manual Slack OAuth flow looks like
Here's a simplified implementation. This is the happy path โ production code needs workspace tracking, scope validation, and Slack-specific error handling layered on top.
import express from 'express';
const app = express();
const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID!;
const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET!;
const REDIRECT_URI = 'http://localhost:3000/slack/callback';
// Step 1: Redirect user to Slack's authorization page
// Note the separate 'user_scope' parameter โ Slack-specific
app.get('/slack/auth', (req, res) => {
const params = new URLSearchParams({
client_id: SLACK_CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'chat:write,channels:read', // Bot scopes
user_scope: 'channels:history,chat:write', // User scopes (different!)
});
res.redirect(`https://slack.com/oauth/v2/authorize?${params}`);
});
// Step 2: Handle the callback โ here's where Slack gets weird
app.get('/slack/callback', async (req, res) => {
const code = req.query.code as string;
if (!code) {
res.status(400).send('No code received');
return;
}
// Step 3: Exchange code for tokens
const tokenResponse = await fetch('https://slack.com/api/oauth.v2.access', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: SLACK_CLIENT_ID,
client_secret: SLACK_CLIENT_SECRET,
code,
redirect_uri: REDIRECT_URI,
}),
});
const data = await tokenResponse.json();
// Slack's non-standard response format:
// - data.access_token = bot token
// - data.authed_user.access_token = user token
// Most OAuth libraries expect a flat access_token, not this nested mess
if (!data.ok) {
console.error('Slack OAuth error:', data.error);
res.status(500).send(`Auth failed: ${data.error}`);
return;
}
const botToken = data.access_token;
const userToken = data.authed_user?.access_token;
const teamId = data.team?.id;
// Step 4: Store tokens, keyed by workspace
// (Remember: tokens are workspace-scoped)
await storeTokens(teamId, { botToken, userToken });
res.send('Connected to Slack!');
});
// Step 5: Use the token โ with Slack's non-standard error format
async function postMessage(channel: string, text: string) {
const tokens = await getStoredTokens(currentTeamId);
const response = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
Authorization: `Bearer ${tokens.botToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ channel, text }),
});
const result = await response.json();
// Slack returns 200 OK even on errors โ check data.ok instead
if (!result.ok) {
throw new Error(`Slack API error: ${result.error}`);
}
return result;
}
app.listen(3000);
Count the Slack-specific gotchas in that code: the nested token response, separate bot and user scopes, workspace-scoped storage, and the ok: false error format that comes back with a 200 status code. An agent that talks to Gmail, GitHub, and Slack needs three completely different OAuth implementations.
The TapAuth approach: one API call, any provider
TapAuth is an access gateway for AI agents. It absorbs all provider-specific complexity โ Slack's nested responses, Google's one-time refresh tokens, GitHub's device flow โ and gives your agent the same simple interface for every provider.
// Step 1: Create a grant โ tell TapAuth what you need
const grantResponse = await fetch(
'https://tapauth.ai/api/v1/grants',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TAPAUTH_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'slack',
scopes: ['channels:read', 'channels:history', 'chat:write'],
}),
}
);
const { grant_id, grant_secret, approve_url } = await grantResponse.json();
// Step 2: The user approves at the approve_url
// (Show this link to the user โ they authorize once)
console.log(`Approve access: ${approve_url}`);
// Step 3: Retrieve the token (poll until approved)
const tokenResponse = await fetch(
`https://tapauth.ai/api/v1/token/${grant_id}`,
{
headers: { 'Authorization': `Bearer ${grant_secret}` },
}
);
const { access_token } = await tokenResponse.json();
// Now use the token directly with Slack's API
const slackResponse = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
Authorization: `Bearer ${access_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: '#general',
text: 'Hello from ChatGPT via TapAuth! ๐ค',
}),
});
const result = await slackResponse.json();
Same two API calls as connecting to Gmail or GitHub. TapAuth normalizes Slack's non-standard token response into a standard access_token. It handles the bot-vs-user token distinction. It manages workspace context. Your agent doesn't need to know any of Slack's quirks โ it gets a working token and uses the Slack API directly.
And when you need to connect the same agent to Google Drive next? Same API, same flow, different provider name. That's the point: authentication is infrastructure, and TapAuth makes it the same infrastructure for every provider.
What you can build with ChatGPT + Slack + TapAuth
With Slack authentication handled, ChatGPT can become a genuine workspace participant:
- Thread summarizer: "Summarize the discussion in #engineering from today โ what decisions were made?"
- Standup bot: "Read everyone's standup updates and flag anything that's blocked or needs my attention"
- Incident responder: "Post a summary of the current P0 incident to #incidents with the timeline from the thread"
- Knowledge finder: "Search Slack for any discussion about the API rate limit changes and summarize what the team decided"
Each of these is a few Slack API calls. The hard part was always getting authenticated โ and now that's one API call via TapAuth.
Scopes and permissions: what to request for Slack
Slack's scope system is more granular than most providers. Request only what your agent needs:
| Scope | What it allows | When to use it |
|---|---|---|
channels:read | List public channels | Channel discovery, workspace overview |
channels:history | Read messages in public channels | Thread summaries, search, context gathering |
chat:write | Post messages | Notifications, standup posts, responses |
users:read | View user profiles | Tagging, identifying who said what |
search:read | Search messages and files | Knowledge retrieval across channels |
Start with channels:read and channels:history for read-only use cases. Add chat:write when your agent needs to post. Avoid broad scopes like admin โ your agent doesn't need workspace-level control.
TapAuth lets you configure scopes per grant, so your summarizer agent can have read-only access while your incident responder gets write permissions. Different agents, different trust levels, same simple API.
Stop fighting Slack's OAuth quirks
Slack is one of the most painful OAuth providers to implement manually โ non-standard responses, workspace-scoped tokens, confusing scope namespaces. Every hour you spend on Slack-specific auth code is an hour not spent on what your ChatGPT integration actually does.
TapAuth normalizes all of this into one API call. Connect ChatGPT to Slack the same way you'd connect it to Gmail, GitHub, or any other OAuth provider โ two API calls, and your agent has a working token.
Get started โ connect ChatGPT to Slack in under five minutes. No client secrets in your agent code. No workspace token management. Just tokens when your agent needs them.