npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chat-adapter/slack

v4.27.0

Published

Slack adapter for chat

Readme

@chat-adapter/slack

npm version npm downloads

Slack adapter for Chat SDK. Configure single-workspace or multi-workspace OAuth deployments.

Installation

pnpm add @chat-adapter/slack

Single-workspace mode

For bots deployed to a single Slack workspace. The adapter auto-detects SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET from environment variables:

import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter(),
  },
});

bot.onNewMention(async (thread, message) => {
  await thread.post("Hello from Slack!");
});

Token rotation

botToken accepts a function returning a string or Promise<string> — the resolver is invoked per API call, so it composes with Slack token rotation (12-hour TTL) or lazy fetch from a secret manager:

createSlackAdapter({
  botToken: async () => await secrets.get("slack-bot-token"),
});

If the resolver is expensive (e.g. a vault round-trip), implement caching inside the resolver itself.

Custom webhook verification

Pass webhookVerifier to replace the built-in HMAC check — useful when verification runs in a proxy or signing layer ahead of your handler:

createSlackAdapter({
  webhookVerifier: async (request, body) => {
    if (!(await myProxy.verify(request))) {
      throw new Error("invalid");
    }
    return true; // or return a string to substitute the verified body
  },
});

If both signingSecret and webhookVerifier are set, signingSecret wins. When using webhookVerifier, you are responsible for replay/timestamp protection — the built-in 5-minute timestamp tolerance only applies to the signingSecret path.

Multi-workspace mode

For apps installed across multiple Slack workspaces via OAuth, omit botToken and provide OAuth credentials instead. The adapter resolves tokens dynamically from your state adapter using the team_id from incoming webhooks.

When you pass any auth-related config (like clientId), the adapter won't fall back to env vars for other auth fields, preventing accidental mixing of auth modes.

import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";

const slackAdapter = createSlackAdapter({
  clientId: process.env.SLACK_CLIENT_ID!,
  clientSecret: process.env.SLACK_CLIENT_SECRET!,
});

const bot = new Chat({
  userName: "mybot",
  adapters: { slack: slackAdapter },
  state: createRedisState(),
});

OAuth callback

The adapter handles the full Slack OAuth V2 exchange. Point your OAuth redirect URL to a route that calls handleOAuthCallback:

import { slackAdapter } from "@/lib/bot";

export async function GET(request: Request) {
  const { teamId } = await slackAdapter.handleOAuthCallback(request, {
    redirectUri: process.env.SLACK_REDIRECT_URI,
  });
  return new Response(`Installed for team ${teamId}!`);
}

If your install flow uses a specific redirect URI, pass the same value here that you used during the authorize step. This is especially useful when one app supports multiple redirect URLs. When no option is provided, the adapter still falls back to redirect_uri on the callback request URL.

Using the adapter outside webhooks

During webhook handling, the adapter resolves tokens automatically from team_id. Outside that context (e.g. cron jobs or background workers), use getInstallation and withBotToken:

const install = await slackAdapter.getInstallation(teamId);
if (!install) throw new Error("Workspace not installed");

await slackAdapter.withBotToken(install.botToken, async () => {
  const thread = bot.thread("slack:C12345:1234567890.123456");
  await thread.post("Hello from a cron job!");
});

withBotToken uses AsyncLocalStorage under the hood, so concurrent calls with different tokens are isolated.

Removing installations

await slackAdapter.deleteInstallation(teamId);

Token encryption

Pass a base64-encoded 32-byte key as encryptionKey to encrypt bot tokens at rest using AES-256-GCM:

openssl rand -base64 32

When encryptionKey is set, setInstallation() encrypts the token before storing and getInstallation() decrypts it transparently.

Socket mode

For environments behind firewalls that can't expose public HTTP endpoints, the adapter supports Slack Socket Mode. Instead of receiving webhooks, the adapter connects to Slack over a WebSocket.

import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter({
      mode: "socket",
      appToken: process.env.SLACK_APP_TOKEN!,
      botToken: process.env.SLACK_BOT_TOKEN!,
    }),
  },
});

Slack app setup for socket mode

  1. Go to your app's settings at api.slack.com/apps
  2. Navigate to Socket Mode and enable it
  3. Generate an App-Level Token with the connections:write scope — this is your SLACK_APP_TOKEN (xapp-...)
  4. Event subscriptions and interactivity still need to be configured, but no public request URL is required

Socket mode is not compatible with multi-workspace OAuth (clientId/clientSecret). It's designed for single-workspace deployments.

Socket mode on serverless (Vercel)

Socket mode requires a persistent WebSocket connection, which doesn't fit the request/response model of serverless functions. The adapter provides a forwarding mechanism to bridge this gap:

  1. A cron job periodically starts a transient socket listener
  2. The listener connects via WebSocket, acks events immediately, and forwards them as HTTP requests to your webhook endpoint
  3. Your existing webhook route processes the forwarded events normally
// api/slack/socket-mode/route.ts
import { after } from "next/server";
import { bot } from "@/lib/bot";

export const maxDuration = 800;

export async function GET(request: Request) {
  const authHeader = request.headers.get("authorization");
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }

  await bot.initialize();

  const slack = bot.getAdapter("slack");
  const webhookUrl = `https://${process.env.VERCEL_URL}/api/webhooks/slack`;

  return slack.startSocketModeListener(
    { waitUntil: (task: Promise<unknown>) => after(() => task) },
    600_000, // 10 minutes
    undefined,
    webhookUrl
  );
}

Schedule the cron job to run every 9 minutes (overlapping with the 10-minute listener duration) to maintain continuous coverage:

// vercel.json
{
  "crons": [
    {
      "path": "/api/slack/socket-mode",
      "schedule": "*/9 * * * *"
    }
  ]
}

Forwarded events are authenticated using the socketForwardingSecret config option (defaults to SLACK_SOCKET_FORWARDING_SECRET env var, falling back to appToken).

Slack app setup

1. Create a Slack app from manifest

  1. Go to api.slack.com/apps
  2. Click Create New App then From an app manifest
  3. Select your workspace and paste the following manifest:
display_information:
  name: My Bot
  description: A bot built with chat-sdk

features:
  bot_user:
    display_name: My Bot
    always_online: true

oauth_config:
  scopes:
    bot:
      - app_mentions:read
      - channels:history
      - channels:read
      - chat:write
      - groups:history
      - groups:read
      - im:history
      - im:read
      - mpim:history
      - mpim:read
      - reactions:read
      - reactions:write
      - users:read

settings:
  event_subscriptions:
    request_url: https://your-domain.com/api/webhooks/slack
    bot_events:
      - app_mention
      - message.channels
      - message.groups
      - message.im
      - message.mpim
      - member_joined_channel
      - assistant_thread_started
      - assistant_thread_context_changed
  interactivity:
    is_enabled: true
    request_url: https://your-domain.com/api/webhooks/slack
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false
  1. Replace https://your-domain.com/api/webhooks/slack with your deployed webhook URL
  2. Click Create

2. Get credentials

After creating the app, go to Basic InformationApp Credentials and copy:

  • Signing Secret as SLACK_SIGNING_SECRET
  • Client ID as SLACK_CLIENT_ID (multi-workspace only)
  • Client Secret as SLACK_CLIENT_SECRET (multi-workspace only)

Single workspace: Go to OAuth & Permissions, click Install to Workspace, and copy the Bot User OAuth Token (xoxb-...) as SLACK_BOT_TOKEN.

Multi-workspace: Enable Manage Distribution under Basic Information and set up an OAuth redirect URL pointing to your callback route.

3. Configure slash commands (optional)

  1. Go to Slash Commands in your app settings
  2. Click Create New Command
  3. Set Command (e.g., /feedback)
  4. Set Request URL to https://your-domain.com/api/webhooks/slack
  5. Add a description and click Save

Configuration

All options are auto-detected from environment variables when not provided. You can call createSlackAdapter() with no arguments if the env vars are set.

| Option | Required | Description | |--------|----------|-------------| | botToken | No | Bot token (xoxb-...) or a function returning one (sync or async) for rotation/lazy fetch. Auto-detected from SLACK_BOT_TOKEN | | signingSecret | No* | Signing secret for webhook verification. Auto-detected from SLACK_SIGNING_SECRET | | webhookVerifier | No* | Custom verifier (request, body) => unknown \| Promise<unknown> used in place of signingSecret. Returning a string substitutes the verified body for downstream parsing | | mode | No | Connection mode: "webhook" (default) or "socket" | | appToken | No** | App-level token (xapp-...) for socket mode. Auto-detected from SLACK_APP_TOKEN | | socketForwardingSecret | No | Shared secret for authenticating forwarded socket events. Auto-detected from SLACK_SOCKET_FORWARDING_SECRET, falls back to appToken | | clientId | No | App client ID for multi-workspace OAuth. Auto-detected from SLACK_CLIENT_ID | | clientSecret | No | App client secret for multi-workspace OAuth. Auto-detected from SLACK_CLIENT_SECRET | | encryptionKey | No | AES-256-GCM key for encrypting stored tokens. Auto-detected from SLACK_ENCRYPTION_KEY | | installationKeyPrefix | No | Prefix for the state key used to store workspace installations. Defaults to slack:installation. The full key is {prefix}:{teamId} | | logger | No | Logger instance (defaults to ConsoleLogger("info")) |

*signingSecret is required for webhook mode — either via config, SLACK_SIGNING_SECRET env var, or a webhookVerifier. **appToken is required for socket mode — either via config or SLACK_APP_TOKEN env var.

Environment variables

SLACK_BOT_TOKEN=xoxb-...             # Single-workspace only
SLACK_SIGNING_SECRET=...             # Required for webhook mode
SLACK_APP_TOKEN=xapp-...             # Required for socket mode
SLACK_SOCKET_FORWARDING_SECRET=...   # Optional, for socket event forwarding auth
SLACK_CLIENT_ID=...                  # Multi-workspace only
SLACK_CLIENT_SECRET=...              # Multi-workspace only
SLACK_ENCRYPTION_KEY=...             # Optional, for token encryption

Features

Messaging

| Feature | Supported | |---------|-----------| | Post message | Yes | | Edit message | Yes | | Delete message | Yes | | File uploads | Yes | | Streaming | Native API | | Scheduled messages | Yes (native, with cancel) |

Rich content

| Feature | Supported | |---------|-----------| | Card format | Block Kit | | Buttons | Yes | | Link buttons | Yes | | Select menus | Yes | | Tables | Block Kit | | Fields | Yes | | Images in cards | Yes | | Modals | Yes |

Conversations

| Feature | Supported | |---------|-----------| | Slash commands | Yes | | Mentions | Yes | | Add reactions | Yes | | Remove reactions | Yes | | Typing indicator | Yes | | DMs | Yes | | Ephemeral messages | Yes (native) |

Message history

| Feature | Supported | |---------|-----------| | Fetch messages | Yes | | Fetch single message | Yes | | Fetch thread info | Yes | | Fetch channel messages | Yes | | List threads | Yes | | Fetch channel info | Yes | | Post channel message | Yes |

Platform-specific

| Feature | Supported | |---------|-----------| | Assistants API | Yes | | Member joined channel | Yes | | App Home tab | Yes |

Slack Assistants API

The adapter supports Slack's Assistants API for building AI-powered assistant experiences. This enables suggested prompts, status indicators, and thread titles in assistant DM threads.

Event handlers

Register handlers on the Chat instance:

bot.onAssistantThreadStarted(async (event) => {
  const slack = bot.getAdapter("slack") as SlackAdapter;
  await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
    { title: "Summarize", message: "Summarize this channel" },
    { title: "Draft", message: "Help me draft a message" },
  ]);
});

bot.onAssistantContextChanged(async (event) => {
  // User navigated to a different channel with the assistant panel open
});

Adapter methods

The SlackAdapter exposes these methods for the Assistants API:

| Method | Description | |--------|-------------| | setSuggestedPrompts(channelId, threadTs, prompts, title?) | Show prompt suggestions in the thread | | setAssistantStatus(channelId, threadTs, status) | Show a thinking/status indicator | | setAssistantTitle(channelId, threadTs, title) | Set the thread title (shown in History) | | publishHomeView(userId, view) | Publish a Home tab view for a user | | startTyping(threadId, status) | Show a custom loading status (requires assistant:write scope) |

Required scopes and events

Add these to your Slack app manifest for Assistants API support:

oauth_config:
  scopes:
    bot:
      - assistant:write

settings:
  event_subscriptions:
    bot_events:
      - assistant_thread_started
      - assistant_thread_context_changed

Stream with stop blocks

When streaming in an assistant thread, you can attach Block Kit elements to the final message:

await thread.post(textStream, {
  stopBlocks: [
    { type: "actions", elements: [{ type: "button", text: { type: "plain_text", text: "Retry" }, action_id: "retry" }] },
  ],
});

Troubleshooting

handleOAuthCallback throws "Adapter not initialized"

  • Call await bot.initialize() before handleOAuthCallback() in your callback route.
  • In a Next.js app, this ensures:
    • state adapter is connected
    • the Slack adapter is attached to Chat
    • installation writes succeed
const slackAdapter = bot.getAdapter("slack");

await bot.initialize();
await slackAdapter.handleOAuthCallback(request);

"Invalid signature" error

  • Verify SLACK_SIGNING_SECRET is correct
  • Check that the request timestamp is within 5 minutes (clock sync issue)
  • If using a custom webhookVerifier, the error also surfaces when the verifier throws or returns a falsy value

Bot not responding to messages

  • Verify event subscriptions are configured
  • Check that the bot has been added to the channel
  • Ensure the webhook URL is correct and accessible

License

MIT