Skip to content

Minimal Slack Bot Setup (No Web Server, WebSocket Only)

Here's how to connect your Node.js agent to Slack using Socket Mode - no Express, no HTTP server, just WebSockets and callbacks.


1. Dependencies

npm install @slack/socket-mode @slack/web-api

That's it. Two packages: - @slack/socket-mode - Receives events via WebSocket - @slack/web-api - Sends messages back to Slack


2. Get Your Tokens

You need TWO tokens:

A. Bot Token (xoxb-...)

  1. Go to https://api.slack.com/apps
  2. Create app → "From scratch"
  3. Click "OAuth & Permissions" in sidebar
  4. Add Bot Token Scopes (all 16):
    app_mentions:read
    channels:history
    channels:join
    channels:read
    chat:write
    files:read
    files:write
    groups:history
    groups:read
    im:history
    im:read
    im:write
    mpim:history
    mpim:read
    mpim:write
    users:read
    
  5. Click "Install to Workspace" at top
  6. Copy the Bot User OAuth Token (starts with xoxb-)

B. App-Level Token (xapp-...)

  1. In same app, click "Basic Information" in sidebar
  2. Scroll to "App-Level Tokens"
  3. Click "Generate Token and Scopes"
  4. Name it whatever (e.g., "socket-token")
  5. Add scope: connections:write
  6. Click "Generate"
  7. Copy the token (starts with xapp-)

3. Enable Socket Mode

  1. Go to https://api.slack.com/apps → select your app
  2. Click "Socket Mode" in sidebar
  3. Toggle "Enable Socket Mode" to ON
  4. This routes your app's interactions and events over WebSockets instead of public HTTP endpoints
  5. Done - no webhook URL needed!

Note: Socket Mode is intended for internal apps in development or behind a firewall. Not for apps distributed via Slack Marketplace.


4. Enable Direct Messages

  1. Go to https://api.slack.com/apps → select your app
  2. Click "App Home" in sidebar
  3. Scroll to "Show Tabs" section
  4. Check "Allow users to send Slash commands and messages from the messages tab"
  5. Save

5. Subscribe to Events

  1. Go to https://api.slack.com/apps → select your app
  2. Click "Event Subscriptions" in sidebar
  3. Toggle "Enable Events" to ON
  4. Important: No Request URL needed (Socket Mode handles this)
  5. Expand "Subscribe to bot events"
  6. Click "Add Bot User Event" and add:
  7. app_mention (required - to see when bot is mentioned)
  8. message.channels (required - to log all channel messages for context)
  9. message.groups (optional - to see private channel messages)
  10. message.im (required - to see DMs)
  11. Click "Save Changes" at bottom

6. Store Tokens

Create .env file:

SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_APP_TOKEN=xapp-your-app-token-here

Add to .gitignore:

echo ".env" >> .gitignore

7. Minimal Working Code

require('dotenv').config();
const { SocketModeClient } = require('@slack/socket-mode');
const { WebClient } = require('@slack/web-api');

const socketClient = new SocketModeClient({ 
  appToken: process.env.SLACK_APP_TOKEN 
});

const webClient = new WebClient(process.env.SLACK_BOT_TOKEN);

// Listen for app mentions (@mom do something)
socketClient.on('app_mention', async ({ event, ack }) => {
  try {
    // Acknowledge receipt
    await ack();

    console.log('Mentioned:', event.text);
    console.log('Channel:', event.channel);
    console.log('User:', event.user);

    // Process with your agent
    const response = await yourAgentFunction(event.text);

    // Send response
    await webClient.chat.postMessage({
      channel: event.channel,
      text: response
    });
  } catch (error) {
    console.error('Error:', error);
  }
});

// Start the connection
(async () => {
  await socketClient.start();
  console.log('⚡️ Bot connected and listening!');
})();

// Your existing agent logic
async function yourAgentFunction(text) {
  // Your code here
  return "I processed: " + text;
}

That's it. No web server. Just run it:

node bot.js

8. Listen to ALL Events (Not Just Mentions)

If you want to see every message in channels/DMs the bot is in:

// Listen to all Slack events
socketClient.on('slack_event', async ({ event, body, ack }) => {
  await ack();

  console.log('Event type:', event.type);
  console.log('Event data:', event);

  if (event.type === 'message' && event.subtype === undefined) {
    // Regular message (not bot message, not edited, etc.)
    console.log('Message:', event.text);
    console.log('Channel:', event.channel);
    console.log('User:', event.user);

    // Your logic here
  }
});

9. Common Operations

Send a message

await webClient.chat.postMessage({
  channel: 'C12345', // or channel ID from event
  text: 'Hello!'
});

Send a DM

// Open DM channel with user
const result = await webClient.conversations.open({
  users: 'U12345' // user ID
});

// Send message to that DM
await webClient.chat.postMessage({
  channel: result.channel.id,
  text: 'Hey there!'
});

List channels

const channels = await webClient.conversations.list({
  types: 'public_channel,private_channel'
});
console.log(channels.channels);

Get channel members

const members = await webClient.conversations.members({
  channel: 'C12345'
});
console.log(members.members); // Array of user IDs

Get user info

const user = await webClient.users.info({
  user: 'U12345'
});
console.log(user.user.name);
console.log(user.user.real_name);

Join a channel

await webClient.conversations.join({
  channel: 'C12345'
});

Upload a file

await webClient.files.uploadV2({
  channel_id: 'C12345',
  file: fs.createReadStream('./file.pdf'),
  filename: 'document.pdf',
  title: 'My Document'
});

10. Complete Example with Your Agent

require('dotenv').config();
const { SocketModeClient } = require('@slack/socket-mode');
const { WebClient } = require('@slack/web-api');

const socketClient = new SocketModeClient({ 
  appToken: process.env.SLACK_APP_TOKEN 
});

const webClient = new WebClient(process.env.SLACK_BOT_TOKEN);

// Your existing agent/AI/whatever
class MyAgent {
  async process(message, context) {
    // Your complex logic here
    // context has: user, channel, etc.
    return `Processed: ${message}`;
  }
}

const agent = new MyAgent();

// Handle mentions
socketClient.on('app_mention', async ({ event, ack }) => {
  await ack();

  try {
    // Remove the @mention from text
    const text = event.text.replace(/<@[A-Z0-9]+>/g, '').trim();

    // Process with your agent
    const response = await agent.process(text, {
      user: event.user,
      channel: event.channel
    });

    // Send response
    await webClient.chat.postMessage({
      channel: event.channel,
      text: response
    });
  } catch (error) {
    console.error('Error processing mention:', error);

    // Send error message
    await webClient.chat.postMessage({
      channel: event.channel,
      text: 'Sorry, something went wrong!'
    });
  }
});

// Start
(async () => {
  await socketClient.start();
  console.log('⚡️ Agent connected to Slack!');
})();

11. Available Event Types

You subscribed to these in step 4:

  • app_mention - Someone @mentioned the bot
  • message - Any message in a channel/DM the bot is in

Event object structure:

{
  type: 'app_mention' or 'message',
  text: 'the message text',
  user: 'U12345', // who sent it
  channel: 'C12345', // where it was sent
  ts: '1234567890.123456' // timestamp
}

12. Advantages of Socket Mode

No web server needed - just run your script
No public URL needed - works behind firewall
No ngrok - works on localhost
Auto-reconnect - SDK handles connection drops
Event-driven - just listen to callbacks


13. Disadvantages

❌ Can't distribute to Slack App Directory (only for your workspace)
❌ Script must be running to receive messages (unlike webhooks)
❌ Max 10 concurrent connections per app


Important Notes

  1. You MUST call ack() on every event or Slack will retry
  2. Bot token (xoxb-) is for sending messages
  3. App token (xapp-) is for receiving events via WebSocket
  4. Connection is persistent - your script stays running
  5. No URL validation needed (unlike HTTP webhooks)

Troubleshooting

"invalid_auth" error

  • Check you're using the right tokens
  • Bot token for WebClient, App token for SocketModeClient

"missing_scope" error

  • Make sure you added all 16 bot scopes
  • Reinstall the app after adding scopes

Not receiving events

  • Check Socket Mode is enabled
  • Check you subscribed to events in "Event Subscriptions"
  • Make sure bot is in the channel (or use channels:join)

Bot doesn't respond to mentions

  • Must subscribe to app_mention event
  • Bot must be installed to workspace
  • Check await ack() is called

That's it. No HTTP server bullshit. Just WebSockets and callbacks.