# How to Integrate Bolt.new with Slack

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Integrate Slack with Bolt.new using incoming webhooks (no OAuth, works in the WebContainer preview) or a full Slack App with bot token for reading channels and handling slash commands (requires deployment for OAuth callbacks and event subscriptions). Start with incoming webhooks — create one in the Slack API dashboard and POST JSON messages from an API route. Deploy to Netlify or Bolt Cloud for full app features.

## Add Slack Notifications and Bots to Your Bolt.new App

Slack is the default communication platform for most software teams, and connecting your Bolt.new app to Slack lets you deliver real-time notifications, trigger workflows, and build internal bots without leaving the tools your team already uses. Whether you want to alert a channel when a new user signs up, send daily digest reports, or build a custom slash command that queries your app's data, Slack's APIs make it straightforward to implement from Bolt.

There are two distinct approaches to Slack integration, and understanding which one to use saves significant setup time. Incoming webhooks are the simplest option: Slack gives you a unique HTTPS URL, and you send messages by POSTing a JSON payload to it. No OAuth flow, no token management, no complex app configuration — just a URL that works immediately. This pattern works perfectly from Bolt's WebContainer preview because it's a standard outbound HTTP call. The trade-off is that incoming webhooks are one-directional (you can only send, not receive or read Slack data) and limited to a single channel per webhook.

A full Slack App with a Bot Token unlocks the complete Slack Web API: posting to any channel, reading messages, listing workspace members, responding to slash commands, and reacting to events. The setup requires creating an app in the Slack API dashboard, configuring OAuth scopes, and installing it to your workspace. Slash commands and event subscriptions require a public webhook URL that Slack can call — which means you need to deploy before testing these features. However, once deployed to Netlify or Bolt Cloud, the full Slack API is available and your Bolt app can behave like a first-class Slack integration.

## Before you start

- A Slack workspace where you have permission to create apps and install integrations
- A Slack API account at api.slack.com (free, uses your Slack login)
- For incoming webhooks: a Slack app created with Incoming Webhooks feature enabled
- For full Slack App: bot token scopes configured (chat:write, channels:read, users:read) and app installed to workspace
- A deployed URL on Netlify or Bolt Cloud for testing slash commands and event subscriptions

## Step-by-step guide

### 1. Create a Slack App and Get Your Incoming Webhook URL

Before writing any code in Bolt, you need to create a Slack App in the Slack API dashboard and generate an incoming webhook URL. This webhook URL is all you need to start sending messages — no OAuth flow, no complex permissions. Go to api.slack.com/apps and click 'Create New App'. Choose 'From scratch', give your app a name (e.g., 'My Bolt App'), and select your Slack workspace. Once created, navigate to 'Incoming Webhooks' in the left sidebar of your app settings and toggle 'Activate Incoming Webhooks' to On. Click 'Add New Webhook to Workspace' at the bottom. Slack will ask you to authorize and select a channel — choose the channel where you want messages to appear (e.g., #notifications or #general). After authorizing, you'll see a webhook URL like `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX`. Copy this URL. For a full Slack App with a bot token, also navigate to 'OAuth & Permissions' and add the scopes you need: `chat:write` (to post messages), `channels:read` (to list channels), and `users:read` (to list workspace members). Then click 'Install to Workspace' and copy the Bot User OAuth Token starting with `xoxb-`. Store both credentials in your Bolt project's `.env` file as `VITE_SLACK_WEBHOOK_URL` for the incoming webhook and never expose the bot token on the client side — it belongs server-side only.

```
# .env file in your Bolt project root
# Incoming webhook URL (safe to use from API routes)
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

# Bot token for full Slack API access (server-side only, never expose to client)
SLACK_BOT_TOKEN=xoxb-your-bot-token-here

# Channel IDs for targeting specific channels
SLACK_NOTIFICATIONS_CHANNEL_ID=C00000000
SLACK_ERRORS_CHANNEL_ID=C00000001
```

**Expected result:** You have a Slack App created at api.slack.com, an incoming webhook URL for at least one channel, and optionally a bot token. These credentials are stored in your Bolt project's .env file.

### 2. Prompt Bolt to Build the Slack Notification API Route

With your credentials in the .env file, prompt Bolt to create the server-side API route that handles Slack message sending. For a Next.js project, this will be an API route at `app/api/slack/notify/route.ts`. For a Vite project, you'll use a Supabase Edge Function. The API route pattern is essential here — your Slack bot token and webhook URL must never be exposed in client-side JavaScript bundles where users can find them in browser DevTools. All Slack API calls happen server-side. The generated route should accept a POST request with a message payload, validate the request, and forward it to Slack using either the incoming webhook URL or the `@slack/web-api` SDK. Bolt will generate the correct handler based on your project type. Review the generated code to ensure environment variables are accessed via `process.env.SLACK_BOT_TOKEN` (Next.js) or `import.meta.env` (Vite/client) — the bot token should only appear in server-side code paths. For the incoming webhook approach, no SDK is needed — a simple `fetch()` POST to the webhook URL with a JSON body is sufficient and works perfectly in the WebContainer preview for outbound calls.

```
// app/api/slack/notify/route.ts
import { NextResponse } from 'next/server';

type Severity = 'info' | 'warning' | 'error';

const SEVERITY_COLORS: Record<Severity, string> = {
  info: '#36a64f',
  warning: '#ffcc00',
  error: '#ff0000',
};

export async function POST(request: Request) {
  const webhookUrl = process.env.SLACK_WEBHOOK_URL;
  if (!webhookUrl) {
    return NextResponse.json({ error: 'SLACK_WEBHOOK_URL not configured' }, { status: 500 });
  }

  const { message, channel, severity = 'info' } = await request.json();

  const payload = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: severity === 'error' ? '🚨 Error Alert' : severity === 'warning' ? '⚠️ Warning' : 'ℹ️ Notification',
        },
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: message,
        },
      },
    ],
    attachments: [
      {
        color: SEVERITY_COLORS[severity as Severity] ?? SEVERITY_COLORS.info,
      },
    ],
    ...(channel ? { channel } : {}),
  };

  const response = await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    const errorText = await response.text();
    return NextResponse.json({ error: `Slack error: ${errorText}` }, { status: 500 });
  }

  return NextResponse.json({ success: true });
}
```

**Expected result:** Bolt generates a working /api/slack/notify endpoint. Sending a POST request to it from your app logic delivers a formatted Block Kit message to your Slack channel.

### 3. Add the Slack Web API for Bot Features (Optional)

If you need more than one-way notifications — such as reading channel history, listing workspace members, or replying in threads — you'll need to use the Slack Web API with your bot token. Install the official `@slack/web-api` package and create a server-side Slack client. This approach lets you call any Slack API method: `conversations.list` to list channels, `users.list` to get workspace members, `chat.postMessage` to post to any channel the bot has joined, and `reactions.add` to add emoji reactions to messages. All these calls are standard outbound HTTPS requests and work perfectly from the WebContainer during development. The key difference from incoming webhooks is that the bot token has a broader set of permissions and can interact with Slack data rather than just pushing messages. To use a method, ensure the corresponding OAuth scope is added to your Slack App in the OAuth & Permissions settings — `channels:read` for listing channels, `users:read` for user data, `reactions:write` for reactions. After adding new scopes, reinstall the app to your workspace to get an updated token.

```
// lib/slack.ts — server-side only, import from API routes and server components
import { WebClient } from '@slack/web-api';

if (!process.env.SLACK_BOT_TOKEN) {
  console.warn('SLACK_BOT_TOKEN is not set — Slack integration disabled');
}

export const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

export async function postMessage(
  channel: string,
  text: string,
  blocks?: object[]
) {
  return slack.chat.postMessage({
    channel,
    text,
    ...(blocks ? { blocks } : {}),
  });
}

export async function listChannels() {
  const result = await slack.conversations.list({
    types: 'public_channel',
    limit: 100,
  });
  return result.channels ?? [];
}

export async function getWorkspaceUsers() {
  const result = await slack.users.list({});
  return (result.members ?? []).filter((m) => !m.is_bot && !m.deleted);
}

export async function addReaction(
  channel: string,
  timestamp: string,
  emoji: string
) {
  return slack.reactions.add({ channel, timestamp, name: emoji });
}
```

**Expected result:** Your app can now call Slack API methods server-side — posting to any channel the bot has joined, listing workspace members, and managing reactions.

### 4. Set Up Slash Commands and Event Subscriptions After Deploying

Slash commands and Slack event subscriptions are the features that make your Bolt app feel like a real Slack integration — users can type /mycommand in any Slack channel and your app responds with live data, or Slack notifies your app when specific events happen (new messages, channel joins, reactions). Both features require Slack to send HTTP POST requests to your app, which means they absolutely cannot work in Bolt's WebContainer preview. The WebContainer has no public-facing URL — it's a browser-local environment with a private networking layer managed by Service Workers. Slack's servers cannot reach `localhost` or the dynamic WebContainer preview URLs. The solution is to deploy first. Once you have a deployed URL on Netlify (`https://your-app.netlify.app`) or Bolt Cloud (`https://your-app.bolt.host`), you can configure slash commands and event subscriptions. In the Slack API dashboard, go to 'Slash Commands' and click 'Create New Command'. Enter your command name (e.g., /stats), the request URL pointing to your deployed handler (e.g., `https://your-app.netlify.app/api/slack/commands`), and a description. For event subscriptions, go to 'Event Subscriptions', enable them, enter your deployed URL as the Request URL (Slack will send a challenge to verify), and subscribe to the bot events you need. Bolt can generate the slash command handler — it needs to respond within 3 seconds or return an acknowledgment and process async.

```
// app/api/slack/commands/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { createClient } from '@supabase/supabase-js';

// Verify request is genuinely from Slack
function verifySlackRequest(body: string, timestamp: string, signature: string): boolean {
  const signingSecret = process.env.SLACK_SIGNING_SECRET!;
  const baseString = `v0:${timestamp}:${body}`;
  const hmac = crypto.createHmac('sha256', signingSecret);
  hmac.update(baseString);
  const computedSig = `v0=${hmac.digest('hex')}`;
  return crypto.timingSafeEqual(Buffer.from(computedSig), Buffer.from(signature));
}

export async function POST(request: NextRequest) {
  const body = await request.text();
  const timestamp = request.headers.get('x-slack-request-timestamp') ?? '';
  const signature = request.headers.get('x-slack-signature') ?? '';

  if (!verifySlackRequest(body, timestamp, signature)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const params = new URLSearchParams(body);
  const command = params.get('command');
  const userId = params.get('user_id');

  if (command === '/stats') {
    const supabase = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.SUPABASE_SERVICE_ROLE_KEY!
    );
    const { count: userCount } = await supabase
      .from('users')
      .select('*', { count: 'exact', head: true });

    return NextResponse.json({
      response_type: 'in_channel',
      blocks: [
        {
          type: 'header',
          text: { type: 'plain_text', text: '📊 App Stats' },
        },
        {
          type: 'section',
          fields: [
            { type: 'mrkdwn', text: `*Total Users*\n${userCount ?? 0}` },
          ],
        },
      ],
    });
  }

  return NextResponse.json({
    response_type: 'ephemeral',
    text: 'Unknown command. Try /stats or /help.',
  });
}
```

**Expected result:** After deploying and configuring the slash command in the Slack API dashboard, typing /stats in any channel where your bot is present returns live stats from your database.

### 5. Deploy and Set Environment Variables

Once your Slack integration code is working in development (outbound webhook notifications can be tested in the preview, but slash commands require deployment), deploy your app to Netlify or Bolt Cloud. When deploying to Netlify, connect your app from Bolt's Settings → Applications → Netlify, or push to GitHub and connect the repo to Netlify. After deploying, add your Slack credentials as environment variables in Netlify: go to Site Configuration → Environment Variables → Add a variable. Add `SLACK_WEBHOOK_URL`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, and any channel IDs you use. Environment variables added to Netlify are injected into your serverless functions at runtime — they're never exposed to the browser. Redeploy after adding new variables for them to take effect. For Bolt Cloud deployments, add the same variables in your project's environment settings. Once deployed and environment variables are set, update the Slack API dashboard with your production URLs: the Request URL for slash commands, the event subscription URL, and register any OAuth redirect URIs. Test the full integration by using your slash command in Slack and verifying messages appear in the correct channels.

```
// netlify.toml — if deploying to Netlify
[build]
  command = "npm run build"
  publish = ".next"

[[redirects]]
  from = "/*"
  to = "/.netlify/functions/__nextjs"
  status = 200

# Add in Netlify Dashboard → Site Configuration → Environment Variables:
# SLACK_WEBHOOK_URL = https://hooks.slack.com/services/...
# SLACK_BOT_TOKEN = xoxb-...
# SLACK_SIGNING_SECRET = your-signing-secret
# SLACK_NOTIFICATIONS_CHANNEL_ID = C00000000
```

**Expected result:** Your app is deployed with Slack credentials set as environment variables. Incoming webhook notifications work, and if configured, slash commands and event subscriptions receive payloads from Slack at your production URL.

## Best practices

- Use incoming webhooks for simple one-way notifications — they're simpler to set up, don't need OAuth, and work in Bolt's preview for outbound calls
- Never expose your Slack bot token (xoxb-) in client-side code — always access it through an API route or server action where it stays in process.env
- Store your Slack signing secret and verify request signatures on every incoming request from Slack to prevent spoofed slash commands and events
- Test outbound Slack messages (webhook notifications) in Bolt's WebContainer preview, but deploy before testing slash commands and event subscriptions which require a public URL
- Use Slack Block Kit for message formatting instead of plain text — it renders much better and supports interactive components like buttons and dropdown menus
- Create a dedicated Slack App for each project environment — use separate apps for development and production to avoid mixing notification channels
- Handle rate limits gracefully: Slack limits Tier 1 methods to 1 request/minute and Tier 3 to 50+ requests/minute — add retry logic with exponential backoff for critical notifications
- Use Slack's message thread reply feature for follow-up information rather than posting multiple messages to the channel, reducing channel noise

## Use cases

### New User Signup Notifications

Send an automatic Slack message to your team's #signups channel every time a new user creates an account. The notification includes the user's email, signup timestamp, and referral source. Incoming webhooks handle this perfectly — no OAuth needed, works in the preview.

Prompt example:

```
Add a Slack notification to my app that sends a message to our team Slack channel whenever a new user signs up. Use an incoming webhook URL stored in the .env file. The message should include the user's email, the signup date, and which page they signed up from. Format it as a Slack Block Kit message with a header and two fields.
```

### Internal Admin Bot with Slash Commands

Build a Slack bot that responds to slash commands like /stats to return your app's key metrics or /users to list recent signups. The bot fetches live data from your Supabase database and formats it as a Slack message response. Requires a deployed URL for Slack to send slash command payloads to.

Prompt example:

```
Create a Slack bot for my app that handles a /dashboard slash command. When someone uses /dashboard in Slack, fetch the last 7 days of signups and revenue from Supabase and respond with a formatted Slack message showing total users, new users this week, and total revenue. Use the Slack Web API with a bot token stored in the .env file. Set up the POST handler at /api/slack/commands.
```

### Automated Error Alerting

Send Slack alerts to an #errors channel when server errors occur, API calls fail, or important background jobs encounter exceptions. Each alert includes the error message, stack trace summary, and a link to the affected page. Critical for production monitoring when you don't want to check logs manually.

Prompt example:

```
Add Slack error alerting to my app. Create a utility function sendSlackAlert(message, severity, context) that posts a formatted Slack Block Kit message to our #errors channel using an incoming webhook. Severity options: 'critical' (red), 'warning' (yellow), 'info' (blue). Call it from the global error handler and from the API route for failed payment processing.
```

## Troubleshooting

### Slack webhook POST returns 'invalid_payload' or messages aren't appearing in the channel

Cause: The JSON payload is malformed, the Content-Type header is missing, or the incoming webhook URL has been deleted or revoked in the Slack App settings.

Solution: Ensure you're sending Content-Type: application/json with your POST request and that the body is valid JSON. Verify the webhook URL in your .env matches the current active webhook in the Slack App dashboard under Incoming Webhooks. Rotating or deleting a webhook in Slack immediately invalidates the URL.

```
// Correct fetch call for incoming webhooks
await fetch(process.env.SLACK_WEBHOOK_URL!, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Hello from Bolt.new!' }),
});
```

### Slash commands or event subscriptions say 'Your URL didn't respond with the value of the challenge parameter'

Cause: Slack sends a verification challenge when you first register an event subscription URL. Your endpoint must respond with the challenge value. This also cannot be tested in Bolt's WebContainer preview — only a deployed URL works.

Solution: Deploy your app first, then register the URL. Ensure your event subscription handler responds to POST requests with the challenge value when Slack sends { type: 'url_verification', challenge: '...' }. The WebContainer preview URL is not accessible by Slack's servers — you must use a deployed Netlify or Bolt Cloud URL.

```
// Handle Slack URL verification challenge
export async function POST(request: Request) {
  const body = await request.json();
  // Slack sends this to verify your endpoint
  if (body.type === 'url_verification') {
    return Response.json({ challenge: body.challenge });
  }
  // Handle real events here
}
```

### Bot token API calls return 'not_in_channel' when posting messages

Cause: The Slack bot hasn't been invited to the channel you're trying to post in. Bots must be explicitly added to channels before they can post.

Solution: In Slack, open the channel you want the bot to post in, type /invite @YourBotName, or go to the channel settings and add the bot as a member. For public channels, you can also call conversations.join with the channel ID using the bot token.

### CORS error when calling Slack API directly from the browser (client-side code)

Cause: Slack's API does not allow direct browser-to-API calls for bot tokens. The Slack Web API must be called server-side because bot tokens cannot be exposed in client-side JavaScript.

Solution: Move all Slack API calls to an API route (app/api/slack/route.ts for Next.js) or a Supabase Edge Function. Never import @slack/web-api or use the bot token in components or client-side hooks. Call your internal API route from the client instead.

## Frequently asked questions

### Can I send Slack messages from Bolt.new without deploying first?

Yes, for outbound messages using incoming webhooks. Create an incoming webhook URL in the Slack API dashboard and POST JSON to it from your API route — this is a standard outbound HTTP call that works in Bolt's WebContainer preview. Slash commands and event subscriptions require a deployed public URL because Slack needs to send HTTP requests to your app, which cannot reach the WebContainer's browser-local environment.

### Does Bolt.new have a native Slack integration like it does for Stripe?

No, Bolt.new does not have a dedicated native Slack panel in Settings like it does for Stripe. Slack integration is implemented through standard API routes using incoming webhooks or the @slack/web-api SDK. Prompt Bolt to 'Add Slack notifications using incoming webhooks' and it will generate the API route and helper functions based on your .env credentials.

### How do I get my Slack bot token and signing secret?

Go to api.slack.com/apps, select your app, and find 'OAuth & Permissions' for the bot token (starts with xoxb-) after installing the app to your workspace. The signing secret is under 'Basic Information' → 'App Credentials' → 'Signing Secret'. You need both for a full Slack App integration — the bot token for making API calls and the signing secret for verifying incoming slash command requests.

### Can I read messages from a Slack channel in my Bolt.new app?

Yes, using the Slack Web API with a bot token. Add the `channels:history` scope to your Slack App, install it to your workspace, invite the bot to the channel, and call slack.conversations.history({ channel: channelId }) from your API route. This is a standard HTTPS request that works in both the WebContainer preview and after deployment.

### How do I send Slack alerts only in production and not during local development?

Use an environment check in your Slack notification helper: only call the Slack API when process.env.NODE_ENV === 'production' or when a specific SLACK_ENABLED=true environment variable is set. In Bolt's WebContainer, set SLACK_ENABLED to false in your .env file and set it to true only in your Netlify or Bolt Cloud environment variables.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/slack
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/slack
