w to Build a Slack AI Assistant with Node.js Part 1

Introduction

AI assistants are quickly becoming part of the modern workplace. Instead of switching between multiple applications, teams increasingly expect to ask questions, generate content, summarize conversations, or automate repetitive tasks directly from the tools they already use every day. Slack has naturally become one of the most popular platforms for bringing these AI capabilities into team workflows.

While Slack offers its own AI features, building a custom Slack AI Assistant gives you complete control over its behavior, integrations, and knowledge. You can connect it to your own applications, internal APIs, company documentation, or business workflows to create an assistant tailored to your organization’s needs.

In this tutorial, you’ll learn how to build a Slack AI Assistant from scratch using Node.js, TypeScript, Slack Bolt, and the OpenAI API. We’ll start with a simple AI-powered chatbot that responds to Slack messages, then gradually evolve it into a production-ready assistant in future articles.

What You’ll Build

In this first part of the series, we’ll build a simple but fully functional Slack AI Assistant that listens for messages in Slack, sends user prompts to the OpenAI API, and streams AI-generated responses back to users in real time.

By the end of this tutorial, you’ll have an assistant capable of:

  • Responding to messages when mentioned in Slack
  • Sending user prompts to an OpenAI Large Language Model (LLM)
  • Streaming AI responses back to Slack as they’re generated for a faster, more interactive user experience
  • Generating intelligent, context-aware answers
  • Running as a Node.js application using the Slack Bolt framework
  • Serving as a solid foundation for more advanced AI capabilities

To keep the implementation easy to understand, we’ll communicate directly with the OpenAI API without adding extra layers such as vector databases, Retrieval-Augmented Generation (RAG), or long-term conversation memory. This is only the beginning. In the next part of this series, we’ll transform this basic chatbot into a much more capable RAG-powered Slack AI Assistant. We’ll integrate a vector database, allow the assistant to answer questions using your own documents, and maintain conversation history by preserving Slack thread context. We’ll also explore advanced capabilities such as conversation memory, citations from internal knowledge sources, and more production-ready AI patterns for enterprise applications.

Demo app showing our Slack AI assistant built using NodeJS and Bolt SDK.

Getting Started

Before we start writing any code, let’s set up everything we’ll need for the project. The setup process is straightforward and should only take a few minutes.

To keep this tutorial focused on building the Slack AI Assistant, the setup instructions below have been intentionally kept brief. If you run into any issues during installation or configuration, don’t worry, I’ve included additional official documentation and troubleshooting resources at the end of this article.

Requirements

Before getting started, make sure you have the following:

  • Node.js 20 or later installed
  • npm (included with Node.js)
  • A Slack workspace where you have permission to install apps
  • An OpenAI account with API access
  • A code editor such as Visual Studio Code
  • Git installed on your machine

Clone the Project

Instead of creating the project from scratch, you can clone the starter repository that we’ll use throughout this tutorial.

git clone https://github.com/jsphkhan/slack-ai-assistant.git
cd slack-ai-assistant
npm install

The repository already contains the basic project structure, Slack Bolt configuration, and the application code that we’ll build upon throughout this guide.

Create a Slack App

Next, we’ll create a Slack application that our assistant will use to communicate with your workspace.

  1. Visit https://api.slack.com/apps/new
  2. Select From a manifest
  3. Choose the workspace where you want to install the assistant
  4. Open the manifest.json file from the cloned repository
  5. Copy and paste its contents into the Slack manifest editor
  6. Create the application
  7. Click Install App and authorize it for your workspace

Using the provided manifest automatically configures the required bot scopes, event subscriptions, and application settings, saving you from configuring everything manually.

Why a Slack App needs to be created?

Create your Slack app from the manifest.json file.

Generate an OpenAI API Key

The assistant uses the OpenAI API to generate AI-powered responses. To create an API key:

  1. Sign in to your OpenAI account.
  2. Navigate to the API Keys section.
  3. Create a new secret key. It will look something like sk-proj-...
  4. Copy the generated key and store it securely.
  5. You’ll use this key later when configuring the application’s environment variables.

Important:

  1. You need an OpenAI Platform account with a billing method attached to generate and use API keys.
  2. Never commit your API key to Git or expose it in your source code. Always store it securely using environment variables.

Once these steps are complete, you’re ready to configure the project and run your first Slack AI Assistant locally.

Understanding the Basics

Why do I need to create a Slack App?

Think of a Slack App as the identity and security layer between Slack and your Node.js application. Your Node.js server cannot simply connect to Slack and start reading or sending messages. Slack needs a secure way to know:

  • Who is connecting?
  • Which workspace is the app allowed to access?
  • What permissions does it have?
  • Where should Slack send events like messages or mentions?

When you create the app, Slack issues credentials such as Bot Tokens and Signing Secrets that your Node.js application uses to authenticate with Slack’s APIs. The app also defines the permissions (OAuth scopes) your assistant needs, such as reading messages, responding to mentions, or posting replies.

Slack Tokens and Permissions

When we created our Slack App using the provided manifest.json, Slack automatically configured the application’s features, permissions, and event subscriptions. This saves us from manually configuring each setting and ensures everyone following this tutorial starts with the same configuration.

The application uses the following credentials:

CredentialPurpose
Bot User OAuth Token (xoxb-...)Authenticates the application when calling Slack APIs to send messages, reply in threads, join channels, and stream AI-generated responses.
App-Level Token (xapp-...)Used by Slack Bolt to establish a secure WebSocket connection with Slack using Socket Mode, allowing the application to receive events without exposing a public HTTP endpoint.
Signing SecretVerifies that incoming requests genuinely originate from Slack, protecting your application from unauthorized or forged requests.

The manifest also grants the bot the OAuth scopes (permissions) it needs to function correctly:

OAuth ScopePurpose
app_mentions:readReceive events when users mention the assistant using @Assistant.
assistant:writeEnables the application to participate in Slack’s AI Assistant experience, including streaming AI responses.
chat:writeSend messages and replies back to Slack conversations.
channels:joinAllow the bot to join public channels when required.
channels:historyRead messages from public channels the bot has access to.
groups:historyRead messages from private channels the bot has been invited to.
im:historyRead messages from direct conversations with users.
commandsHandle Slack slash commands if the application defines them.

Finally, the manifest subscribes the application to several Slack events, including app mentions, direct messages, channel messages, and AI Assistant thread events. Whenever one of these events occurs, Slack delivers it to our Node.js application over Socket Mode, allowing the assistant to process the request and generate an AI-powered response.

This is how the scoped would look like for our Slack app

Setting Up Environment Variables

Our application requires a few configuration values to connect securely with Slack and the OpenAI API. Rather than hardcoding these values into the source code, we’ll store them as environment variables.

Create a .env (clone .env.example and rename as .env) file in the project’s root directory and add the following variables:

Environment VariablePurpose
SLACK_BOT_TOKENAuthenticates your application when calling Slack APIs to send messages, reply in threads, and stream AI responses.
SLACK_APP_TOKENUsed by Slack Bolt to establish a secure connection with Slack when using Socket Mode.
SLACK_SIGNING_SECRETVerifies that incoming requests genuinely originate from Slack, protecting your application from unauthorized requests.
OPENAI_API_KEYAuthenticates your application when making requests to the OpenAI API.
OPENAI_MODELSpecifies the OpenAI model the assistant will use to generate responses. We’ll use gpt-4o-mini throughout this tutorial.
PORTThe local port on which the Node.js application will run.

What is Socket Mode?

Socket Mode is an alternative way for your Slack application to receive events from Slack. Normally, Slack sends events (such as app mentions or messages) to your application using an HTTP webhook endpoint. That means your Node.js application must be publicly accessible over HTTPS so Slack can reach it.

With Socket Mode, your application opens an outbound WebSocket connection to Slack instead. Slack then delivers all events through that secure connection, so you don’t need to expose a public URL. Our project uses Socket Mode, which is why you’ll see the SLACK_APP_TOKEN environment variable in the application configuration. For beginners, it’s actually the easier option because they don’t have to:

  • buy a domain
  • configure HTTPS
  • expose localhost with ngrok
  • configure Slack Request URLs

How to Generate the Slack Bot Token and App Token

Our application requires two Slack tokens: a Bot User OAuth Token (xoxb-...) and an App-Level Token (xapp-...). Both can be generated from your Slack App dashboard.

Generate the Bot User OAuth Token

  1. Open your Slack App at https://api.slack.com/apps.
  2. Select your application.
  3. Navigate to OAuth & Permissions.
  4. Click Install to Workspace (or Reinstall to Workspace if you’ve updated the app).

After installation, copy the Bot User OAuth Token (xoxb-...) to SLACK_BOT_TOKEN inside .env file.

Generating Bot User token

Generate the App-Level Token

  1. In your Slack App dashboard, navigate to Basic Information.
  2. Scroll down to App-Level Tokens.
  3. Click Generate Token and Scopes.
  4. Give the token a name (for example, Socket Mode).
  5. Add the connections:write scope.

Generate the token and copy the App-Level Token (xapp-...) to SLACK_APP_TOKEN inside .env file.

Generating App level token

You’ll use both tokens in the .env file when configuring the application.

How Slack Communicates with Your Application

Before we start writing code, it’s helpful to understand how Slack, your Node.js application, and the OpenAI API work together. Whenever a user interacts with your assistant, several components work together behind the scenes to generate an AI-powered response.

The overall flow looks like this:

High-level architecture of a Slack AI Assistant built with Node.js, Slack Bolt, Socket Mode, and the OpenAI API, showing how user messages are processed and AI responses are streamed back to Slack.

Here’s what happens step by step:

  1. User sends a message – A user mentions the assistant (for example, @Slack AI Assistant) or starts an AI Assistant conversation.
  2. Slack receives the event – Slack detects the interaction and sends an event to our application over Socket Mode.
  3. Slack Bolt processes the event – Our Node.js application receives the event, extracts the user’s message, and prepares a prompt for the language model.
  4. Call the OpenAI API – The application sends the user’s prompt to the configured OpenAI model.
  5. Generate a streaming response – OpenAI generates the response and streams it back to our application token by token.
  6. Send the response to Slack – Our application forwards the streamed response back to the Slack conversation in real time.
  7. Display the final answer – The user sees the AI-generated response directly inside Slack without leaving the conversation.

With the fundamentals out of the way, it’s time to dive into the implementation. We’ll begin by exploring the project structure, then configure our application, connect it to Slack and the OpenAI API, and finally build a fully functional Slack AI Assistant capable of generating streaming AI responses in real time.

Project Structure

Before we start changing code, let’s quickly understand how the project is organized.

This repository is intentionally small and focused. It keeps the Slack setup, OpenAI integration, and assistant event handling in separate files so the code is easy to follow and extend later.

slack-ai-assistant/
├── manifest.json                 # Slack App manifest
├── package.json                  # Project dependencies and scripts
├── package-lock.json
├── tsconfig.json                 # TypeScript configuration
├── README.md

├── scripts/
│   └── verify-env.ts             # Validates required environment variables

└── src/
    ├── app.ts                    # Application entry point
    ├── env.ts                    # Environment variable helpers

    ├── agent/
    │   └── llm-caller.ts         # OpenAI integration and streaming responses

    └── listeners/
        └── assistant/
            └── index.ts          # Slack AI Assistant event handlers
FilePurpose
manifest.jsonDefines the Slack App configuration, permissions, Socket Mode, bot events, and AI assistant settings.
src/app.tsMain application file. It initializes Slack Bolt, enables Socket Mode, registers listeners, and starts the app.
src/env.tsContains small helper functions to read and validate environment variables.
src/agent/llm-caller.tsHandles the OpenAI API call and streams the model response back to Slack.
src/listeners/assistant/index.tsRegisters Slack Assistant event handlers such as thread start, context changes, and user messages.
scripts/verify-env.tsChecks whether the required environment variables are available before running the app.
package.jsonDefines dependencies and scripts such as npm run dev, npm start, and npm run typecheck.

The most important files for this tutorial are:

  • src/app.ts
  • src/agent/llm-caller.ts
  • src/listeners/assistant/index.ts

We will focus mainly on these files because they contain the core logic of the Slack AI Assistant: receiving Slack messages, sending prompts to OpenAI, and streaming AI-generated responses back into Slack.

Application Entry Point

The src/app.ts file is the starting point of our Slack AI Assistant. It brings together all the components we’ve discussed so far—loading configuration, creating the Slack Bolt application, registering listeners, and finally starting the application. Let’s walk through each step.

1. Load Environment Variables

The application begins by loading the required environment variables and reading the Slack credentials from the .env file.

import "dotenv/config";

const botToken = requiredEnv("SLACK_BOT_TOKEN");
const signingSecret = requiredEnv("SLACK_SIGNING_SECRET");
const appToken = process.env.SLACK_APP_TOKEN;
const socketMode = Boolean(appToken);

If an App Token is available, the application automatically enables Socket Mode.

2. Create the Slack Bolt Application

Next, we create a new Slack Bolt application by providing the required credentials and configuration.

const app = new App({
  token: botToken,
  signingSecret,
  socketMode,
  appToken: appToken ?? undefined,
  port: socketMode ? undefined : optionalNumberEnv("PORT", 3000),
  logLevel: LogLevel.INFO,
});

This initializes our Slack application and establishes the connection with Slack using either Socket Mode or the HTTP Events API.

3. Register Event Listeners

Once the application has been created, we register our Slack AI Assistant listeners.

registerAssistantListeners(app);

These listeners are responsible for handling assistant events such as new conversations, thread updates, and user interactions. We’ll explore them in more detail in the next section.

4. Start the Application

Finally, the application authenticates with Slack and starts listening for incoming events.

const auth = await app.client.auth.test({ token: botToken });
await app.start();

If everything is configured correctly, you’ll see a message similar to the following in your terminal:

Slack AI Assistant is running in Socket Mode as slack_ai_assistant.

At this point, your application is connected to Slack and ready to receive user messages.

Handling Slack Events

Now that our application is running, it’s time to respond to user interactions.

Slack notifies our application whenever an event occurs, such as a user mentioning the assistant, starting a new AI Assistant conversation, or sending a message. We register handlers for these events in src/listeners/assistant/index.ts.

The application registers all assistant-related event handlers using a single function:

registerAssistantListeners(app);

This keeps the application’s entry point clean while separating all Slack Assistant logic into its own module.

assistant_thread_started

This event is triggered when a user starts a new conversation with the Slack AI Assistant.

In our application, we:

  • Save the thread context.
  • Send a welcome message.
  • Display a few suggested prompts to help users get started.
threadStarted: async ({ saveThreadContext, say, setSuggestedPrompts }) => {
  await saveThreadContext();

  await say("Hi, I am your Slack AI assistant. What can I help with?");

  await setSuggestedPrompts({
    title: "Try asking",
    prompts: [
      {
        title: "Summarize this channel",
        message: "Summarize the current channel context.",
      },
      {
        title: "Draft a reply",
        message: "Draft a concise reply for this discussion.",
      },
    ],
  });
}

assistant_thread_context_changed

Slack invokes this event whenever the conversation context changes.

Our implementation simply saves the updated thread context, allowing the assistant to maintain context as the conversation evolves.

threadContextChanged: async ({ saveThreadContext }) => {
  await saveThreadContext();
}

Although our simple assistant doesn’t use this context yet, it becomes extremely useful when we add conversation memory and RAG in the next part of this series.

app_mention

The application also supports the traditional Slack bot experience using app_mention events.

Whenever a user mentions the bot (for example, @Slack AI Assistant), Slack forwards the event to our application. We extract the user’s message, send it to the OpenAI API, and stream the generated response back into the same conversation.

This allows users to interact with the assistant directly from any Slack channel where the bot has been added.

Note: In this project, the app_mention event is registered in src/app.ts, while the Slack AI Assistant events are organized separately in src/listeners/assistant/index.ts.

Processing User Messages

The most important handler is userMessage, which is executed whenever a user sends a message to the assistant.

After validating the incoming message, we:

  1. Extract the user’s prompt.
  2. Update the assistant title.
  3. Display a “thinking…” status.
  4. Send the prompt to the OpenAI API.
  5. Stream the AI-generated response back to Slack.
await setTitle(text.slice(0, 80));
await setStatus("thinking...");

const streamer = sayStream();

await callLLM(streamer, [
  {
    role: "system",
    content:
      "You are a concise Slack AI assistant. Answer clearly and format responses for Slack markdown.",
  },
  {
    role: "user",
    content: text,
  },
]);

await streamer.stop();
await setStatus("");

Notice that we use sayStream() instead of say(). This enables the assistant to stream tokens back to Slack in real time, providing a much smoother user experience than waiting for the complete response before displaying it.

Integrating the OpenAI API & Generating AI Responses

With our Slack event handlers in place, the next step is to generate AI-powered responses. All OpenAI-related logic is kept in src/agent/llm-caller.ts, making it easy to maintain or swap models in the future.

Creating the OpenAI Client

We begin by creating an OpenAI client using the API key stored in our environment variables. We also read the model name from the configuration so it can be changed without modifying the source code.

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});
const model = process.env.OPENAI_MODEL ?? "gpt-4o-mini";

Sending the Prompt

When a user sends a message, the userMessage handler prepares the conversation and calls the callLLM() function. The prompt contains both the system message and the user’s message, giving the model the context it needs to generate an appropriate response.

await callLLM(streamer, [
  {
    role: "system",
    content:
      "You are a concise Slack AI assistant. Answer clearly and format responses for Slack markdown.",
  },
  {
    role: "user",
    content: text,
  },
]);

Streaming AI Responses

Instead of waiting for the entire response to be generated, we enable streaming by setting stream: true. As OpenAI produces each token, it’s immediately forwarded to Slack, allowing users to see the response appear in real time.

const response = await openai.responses.create({
  model,
  input,
  stream: true,
});

for await (const event of response) {
  if (event.type === "response.output_text.delta" && event.delta) {
    await streamer.append({
      markdown_text: event.delta,
    });
  }
}

This streaming approach provides a faster and more interactive experience compared to waiting for the complete response before displaying it. It’s also one of the key features that makes modern AI assistants feel responsive and natural to use.

Running the Application

With everything configured, it’s time to start the Slack AI Assistant.

Run the following command from the project root:

npm run dev

Open your Slack workspace, start a new AI Assistant conversation & begin asking questions. Your assistant should generate and stream AI-powered responses in real time.

Start the Slack AI assistant built using NodeJS inside your Slack's AI agents panel on top right. This will open a split panel

Testing the Assistant

Congratulations! You now have a fully functional Slack AI Assistant powered by the OpenAI API. Try asking questions such as:

  • What is Retrieval-Augmented Generation (RAG)?
  • Who is the 46th President of the USA?
  • etc

Test your Slack AI assistant built using NodeJS by typing few messages and having a conversation with the AI agent

Source Code

The complete source code for this tutorial is available on GitHub.

GitHub Repository: https://github.com/jsphkhan/slack-ai-assistant

Feel free to clone the repository, experiment with the code, and use it as a starting point for your own Slack AI Assistant projects.

What’s Next?

In this tutorial, we built a simple Slack AI Assistant that communicates directly with the OpenAI API and streams AI-generated responses back to Slack. While this is a great starting point, the assistant doesn’t yet have knowledge of your organization’s documents or previous conversations. In the next part of this series, we’ll transform it into a production-ready AI assistant by adding:

  • Retrieval-Augmented Generation (RAG) for answering questions from your own documents.
  • Conversation Memory to maintain context across Slack threads.
  • Thread Context to provide more accurate and personalized responses.
  • A Vector Database for efficient semantic search over your knowledge base.

By the end of Part 2, you’ll have a Slack AI Assistant capable of answering questions using your own documentation while maintaining context throughout the conversation, bringing it much closer to a real-world enterprise AI assistant.

References


If you enjoyed this article, you may also like my other articles: