Atlas
PluginsInteractions

Chat SDK Bridge

Unified chat interaction plugin bridging Slack, Teams, Discord, Telegram, Linear, WhatsApp, and more into Atlas via Chat SDK adapters.

The Chat SDK bridge plugin (@useatlas/chat) is the unified interaction layer for Atlas chat platforms. It bridges Chat SDK (vercel/chat) adapters into the Atlas plugin system, replacing the standalone @useatlas/slack and @useatlas/teams plugins with a single, multi-platform plugin.

Instead of maintaining separate plugins per platform, @useatlas/chat wraps Chat SDK adapters and provides built-in state management, error scrubbing, and conversation threading — all configured from a single chatPlugin() call.

Quick Navigation

Jump to the platform or topic you need:

TopicSection
State persistenceState Adapters
Streaming responsesStreaming
Rich cards & formattingRich Cards
Interactive buttonsQuick-Action Buttons
CSV file exportFile Upload (CSV Export)
Status reactionsStatus Reactions
Error scrubbingError Scrubbing
Ephemeral messagesEphemeral Messages
Proactive DMsProactive Direct Messages
Migration from deprecated pluginsMigrating from @useatlas/slack / Migrating from @useatlas/teams
Mounted routes referenceMounted Routes
Environment variablesEnvironment Variables

Installation

bun add @useatlas/chat

Peer Dependencies

  • hono (^4.0.0)
  • zod (^4.0.0)
  • @useatlas/plugin-sdk (>=0.0.1)

Available Adapters

PlatformChat SDK AdapterStatus
Slack@chat-adapter/slackStable
Microsoft Teams@chat-adapter/teamsStable
Discord@chat-adapter/discordStable
Google Chat@chat-adapter/gchatStable
Telegram@chat-adapter/telegramStable
GitHub@chat-adapter/githubStable
Linear@chat-adapter/linearStable
WhatsApp@chat-adapter/whatsappStable

Stable adapters are bundled as dependencies of @useatlas/chat — no separate install needed.

Configuration

import { defineConfig } from "@atlas/api/lib/config";
import { chatPlugin } from "@useatlas/chat";

export default defineConfig({
  plugins: [
    chatPlugin({
      adapters: {
        slack: {
          botToken: process.env.SLACK_BOT_TOKEN!,
          signingSecret: process.env.SLACK_SIGNING_SECRET!,
        },
      },
      executeQuery: myQueryFunction,
    }),
  ],
});

Multi-Platform with State Persistence

export default defineConfig({
  plugins: [
    chatPlugin({
      adapters: {
        slack: {
          botToken: process.env.SLACK_BOT_TOKEN!,
          signingSecret: process.env.SLACK_SIGNING_SECRET!,
          clientId: process.env.SLACK_CLIENT_ID,
          clientSecret: process.env.SLACK_CLIENT_SECRET,
        },
        teams: {
          appId: process.env.TEAMS_APP_ID!,
          appPassword: process.env.TEAMS_APP_PASSWORD!,
          tenantId: process.env.TEAMS_TENANT_ID,
        },
      },
      state: { backend: "pg" },
      executeQuery: myQueryFunction,
      actions: myActionCallbacks,
      conversations: myConversationCallbacks,
    }),
  ],
});

Options

OptionTypeRequiredDescription
adaptersobjectYesPlatform adapter credentials (at least one required).
adapters.slack.botTokenstringYes*Slack bot token (xoxb-...).
adapters.slack.signingSecretstringYes*Slack signing secret for request verification.
adapters.slack.clientIdstringNoClient ID for multi-workspace OAuth.
adapters.slack.clientSecretstringNoClient secret for multi-workspace OAuth.
adapters.teams.appIdstringYes*Microsoft App ID from Azure Bot registration.
adapters.teams.appPasswordstringYes*Microsoft App Password from Azure Bot registration.
adapters.teams.tenantIdstringNoRestrict to a specific Microsoft Entra ID tenant.
adapters.discord.botTokenstringYes*Discord bot token.
adapters.discord.applicationIdstringYes*Discord application ID.
adapters.discord.publicKeystringYes*64-character hex string (Ed25519 public key from Discord Developer Portal).
adapters.discord.mentionRoleIdsstring[]NoRole IDs that trigger mention handlers (in addition to direct @mentions).
adapters.gchat.credentialsobjectNo*Service account credentials (client_email, private_key, optional project_id).
adapters.gchat.useApplicationDefaultCredentialstrueNo*Use Application Default Credentials.
adapters.gchat.endpointUrlstringNoHTTP endpoint URL for card button click actions.
adapters.gchat.pubsubTopicstringNoPub/Sub topic for Workspace Events (projects/{project}/topics/{topic}).
adapters.gchat.impersonateUserstringNoUser email for domain-wide delegation.
adapters.telegram.botTokenstringYes*Telegram bot token from BotFather.
adapters.telegram.secretTokenstringNoWebhook secret token for request verification.
adapters.github.appIdstringYes*GitHub App ID. Required for App-based auth.
adapters.github.privateKeystringYes*GitHub App private key (PEM format).
adapters.github.installationIdnumberNoInstallation ID for single-tenant mode.
adapters.github.tokenstringYes*Personal Access Token (alternative to App auth).
adapters.github.webhookSecretstringNoWebhook secret for HMAC-SHA256 verification.
adapters.github.userNamestringNoBot username for @-mention detection.
adapters.linear.apiKeystringYes*Personal API key from Linear Settings.
adapters.linear.accessTokenstringYes*Pre-obtained OAuth access token.
adapters.linear.clientIdstringYes*OAuth application client ID.
adapters.linear.clientSecretstringYes*OAuth application client secret. Required with clientId.
adapters.linear.webhookSecretstringNoWebhook signing secret for HMAC-SHA256 verification.
adapters.linear.userNamestringNoBot display name for @-mention detection.
adapters.whatsapp.phoneNumberIdstringYes*WhatsApp Business phone number ID.
adapters.whatsapp.accessTokenstringYes*System User access token for Cloud API.
adapters.whatsapp.verifyTokenstringYes*Verify token for webhook challenge-response.
adapters.whatsapp.appSecretstringYes*Meta App Secret for HMAC-SHA256 signature verification.
adapters.whatsapp.userNamestringNoBot display name.
adapters.whatsapp.apiVersionstringNoMeta Graph API version (default: "v21.0").
stateobjectNoState backend config (default: { backend: "memory" }).
state.backend"memory" | "pg" | "redis"NoState backend to use. Default: "memory".
state.tablePrefixstringNoTable name prefix for PG backend. Must be a valid SQL identifier. Default: "chat_".
state.redisUrlstringNoRedis connection URL (future — not yet implemented).
slashCommandNamestringNoSlash command name (e.g. "/data-query"). Must start with / followed by lowercase alphanumeric + hyphens. Default: "/atlas".
executeQueryfunctionYesCallback to run the Atlas agent on a question.
executeQueryStreamfunctionNoStreaming query callback — returns { stream, result }. See Streaming.
streamingobjectNoStreaming configuration.
streaming.enabledbooleanNoEnable streaming responses. Default: true.
streaming.chunkIntervalMsnumberNoMinimum interval (ms) between edits on non-streaming platforms. Range: 200–10000. Default: 500.
checkRateLimitfunctionNoOptional rate limiting callback.
scrubErrorfunctionNoOptional error scrubbing callback.
actionsActionCallbacksNoOptional action framework callbacks (approve, deny, get).
conversationsConversationCallbacksNoOptional conversation persistence callbacks (create, addMessage, get, generateTitle).
fileUploadobjectNoCSV file upload configuration.
fileUpload.autoAttachThresholdnumberNoRow count above which CSV files are auto-attached. Set to 0 to disable. Default: 20.
fileUpload.webBaseUrlstringNoAtlas web UI base URL for fallback links on platforms without file upload (GitHub, Linear).
reactionsobjectNoStatus reaction configuration. See Status Reactions.
reactions.enabledbooleanNoEnable emoji reactions on user messages. Default: true.
reactions.customEmojiobjectNoOverride default emoji for each lifecycle stage (received, processing, complete, error).

* Required when that adapter is configured. At least one adapter must be present.

For Slack OAuth, clientId and clientSecret must both be provided together.

State Adapters

The state backend controls how thread subscriptions, conversation history, and distributed locks are persisted.

BackendDescriptionPersistenceProduction Ready
memoryIn-memory store. State lost on restart.NoneNo
pgPostgreSQL via Atlas internal DB (DATABASE_URL).FullYes
redisRedis (stub — not yet implemented).No
chatPlugin({
  adapters: { slack: { /* ... */ } },
  state: {
    backend: "pg",
    tablePrefix: "chat_", // default — customize to avoid conflicts
  },
  executeQuery: myQueryFunction,
})

The pg backend requires DATABASE_URL to be configured. It auto-creates three tables (chat_subscriptions, chat_locks, chat_cache) using CREATE TABLE IF NOT EXISTS on first connection.

For production deployments, use the pg backend. The memory backend is suitable for development and testing only — state is lost when the process restarts.

How It Works

The plugin bridges Chat SDK events to the Atlas agent:

  1. Slash commands (configurable, default /atlas <question>) — Posts a "Thinking about: <question>..." indicator, subscribes the thread, runs executeQuery, edits the response with a JSX card (or markdown fallback)
  2. @mentions — Subscribes to the thread, runs executeQuery, posts the result as a native JSX card (Block Kit on Slack, Adaptive Cards on Teams, Discord Embeds) with markdown fallback
  3. Thread follow-ups — Messages in subscribed threads are routed through executeQuery with conversation history, posted as a JSX card with markdown fallback
  4. Actions — Approval button clicks call actions.approve() or actions.deny(), then update the original message. Quick-action buttons (Run Again, Export CSV) appear on query result cards
  5. Modals (Slack only) — Clarification dialogs collect structured input and feed responses back into the conversation

Rich Cards

Query results render as platform-native cards using the Chat SDK JSX runtime. Cards include the answer, SQL code block, data table preview, and metadata (steps, tokens). Telegram, GitHub, Linear, and WhatsApp receive formatted text with the markdown fallback.

Card components are exported for host customization:

import { buildQueryResultCard, buildErrorCard, buildApprovalCardJSX, buildDataTableCard } from "@useatlas/chat";
// Or from the dedicated cards entrypoint:
import { buildQueryResultCard } from "@useatlas/chat/cards";

Configurable Slash Command

By default, the bridge registers /atlas as the slash command. To use a custom name (e.g., if you want /data or /query), set slashCommandName:

chatPlugin({
  adapters: { slack: { /* ... */ } },
  slashCommandName: "/data-query",
  executeQuery: myQueryFunction,
})

The command name must start with / followed by lowercase alphanumeric characters or hyphens. Remember to register the matching slash command in your platform's app configuration (e.g., Slack App Manifest, Discord Application Commands).

Quick-Action Buttons

Query result cards include interactive buttons when SQL was executed:

  • Run Again — Re-executes the same SQL query and posts updated results in the thread
  • Export CSV — Re-executes the query and uploads results as a CSV file attachment

Buttons are rendered as platform-native interactive components (Block Kit buttons on Slack, Adaptive Card actions on Teams, Discord buttons). On platforms with limited interactivity, the card includes a text fallback with instructions:

To run again, re-send the same question. To export, ask "export the last result as CSV".

File Upload (CSV Export)

Query results can be exported as CSV file attachments. This happens in three ways:

  1. Export CSV button — Clicking the "Export CSV" button on a query result card re-executes the SQL and uploads the full result as a CSV file
  2. Auto-attach — When query results exceed the configured row threshold (default: 20 rows), a CSV file is automatically attached alongside the card response
  3. Keyword detection — When the user's question contains export keywords ("export", "download", "send as CSV", "as csv", "csv file", "to csv"), the CSV is always attached regardless of row count

Platform support

PlatformFile UploadFallback
SlackYes
TeamsYes
DiscordYes
Google ChatYes
TelegramYes
WhatsAppYes
GitHubNoLink to web UI
LinearNoLink to web UI

On platforms without file upload support, a "View full results" link to the Atlas web UI is posted instead. Configure fileUpload.webBaseUrl to enable this:

chatPlugin({
  adapters: { /* ... */ },
  executeQuery: myQueryFunction,
  fileUpload: {
    autoAttachThreshold: 50,        // attach CSV when > 50 rows (default: 20)
    webBaseUrl: "https://app.example.com", // fallback link for GitHub/Linear
  },
})

CSV files include a UTF-8 BOM for Excel compatibility and follow RFC 4180 formatting (proper quoting and escaping of commas, newlines, and double quotes).

Interactive Capabilities by Platform

FeatureSlackTeamsDiscordGoogle ChatTelegramGitHubLinearWhatsApp
Slash commandsYesYesYesYesYesNoNoNo
Action buttonsYesYesYesYesPartial (inline keyboard)NoNoNo
File uploadsYesYesYesYesYesNoNoYes
Status reactionsYesLimitedYesYesYesNoNoLimited
ModalsYesNoNoNoNoNoNoNo

Slack Modals

On Slack, the bridge supports modals for parameter collection. When the agent needs clarification (e.g., date range, segment selection), a modal dialog can be opened with input fields.

The bridge exposes an openClarificationModal method for host integrations that need to collect structured input:

// From an action handler or slash command context
await bridge.openClarificationModal(event, "What date range would you like to analyze?");

The modal collects user input and feeds the response back into the conversation thread. Modal submissions are validated server-side — empty responses are rejected with an error message displayed in the modal.

Modals are currently supported only on Slack. On other platforms, clarifying questions are handled through regular thread messages.

Status Reactions

The bridge adds emoji reactions to user messages as visual feedback during query processing:

StageEmojiMeaning
Receivedeyes (👀)"I see your message"
Processinghourglass (⏳)"Running your query"
Completecheck (✅)"Query finished successfully"
Errorwarning (⚠️)"Something went wrong"

Reactions are managed via Chat SDK's type-safe emoji API — no raw Unicode strings. Each transition removes the previous reaction and adds the new one.

Platform support

PlatformReactionsNotes
SlackFullNative emoji reactions
DiscordFullUnicode emoji reactions
Google ChatFullUnicode emoji reactions
TelegramFullMessage reactions
TeamsLimitedRead-only — reactions may silently no-op
WhatsAppLimitedReactions may silently no-op
GitHubN/AGraceful no-op
LinearN/AGraceful no-op

Reactions are always best-effort — failures are logged at debug level and never fail a query. On platforms where reactions aren't supported, the lifecycle silently does nothing.

Configuration

Reactions are enabled by default. To disable or customize:

import { emoji } from "chat";

chatPlugin({
  adapters: { slack: { /* ... */ } },
  executeQuery: myQueryFunction,
  reactions: {
    enabled: true, // default — set to false to disable
    customEmoji: {
      received: emoji.sparkles, // override 👀 with ✨
      complete: emoji.rocket,   // override ✅ with 🚀
    },
  },
})

For workspaces with custom emoji installed (Slack, Discord), use emoji.custom():

reactions: {
  customEmoji: {
    received: emoji.custom("atlas_thinking"),
    complete: emoji.custom("atlas_done"),
  },
}

Disabling reactions

Set reactions.enabled to false to skip all reaction lifecycle calls:

chatPlugin({
  adapters: { /* ... */ },
  executeQuery: myQueryFunction,
  reactions: { enabled: false },
})

Streaming

When executeQueryStream is provided and streaming.enabled is not false, the bridge streams agent responses incrementally to chat platforms instead of waiting for the full result.

How it works

  • Slack — Uses Chat SDK's native streaming API for real-time token-by-token updates
  • Teams, Discord, Google Chat — Posts an initial message then edits it with new content at the configured chunkIntervalMs interval (post + edit pattern)
  • Telegram — Posts an initial message then progressively edits it (same post + edit pattern)
  • GitHub — Accumulates the full stream and posts as a single comment (no progressive edits due to API constraints)
  • Linear — Accumulates the full stream and posts as a single comment (same as GitHub)
  • WhatsApp — Buffers the full stream and sends as a single message (WhatsApp does not support message editing)

The bridge passes the async iterable from executeQueryStream directly to Chat SDK's thread.post(), which automatically selects the best transport per platform.

Configuration

import { chatPlugin } from "@useatlas/chat";
import type { StreamChunk } from "@useatlas/chat";

chatPlugin({
  adapters: {
    slack: { botToken: "xoxb-...", signingSecret: "..." },
    teams: { appId: "...", appPassword: "..." },
  },
  executeQuery: myQueryFunction, // always required — used when streaming is disabled
  streaming: {
    enabled: true,        // default: true
    chunkIntervalMs: 1000, // override Chat SDK default of 500ms — edit throttle for Teams/Discord/GChat
  },
  executeQueryStream: (question, options) => {
    // Return a stream and a promise for the final structured result
    const agentResult = runAgent(question, options);

    return {
      stream: agentResult.textStream, // AsyncIterable<string | StreamChunk>
      result: agentResult.finalResult, // Promise<ChatQueryResult>
    };
  },
})

Status indicators

The stream can yield structured StreamChunk objects for rich status updates during agent reasoning:

executeQueryStream: (question) => {
  const stream = async function* () {
    yield { type: "task_update", id: "think", title: "Analyzing question...", status: "in_progress" } as StreamChunk;
    yield { type: "task_update", id: "sql", title: "Running SQL...", status: "in_progress" } as StreamChunk;
    yield { type: "task_update", id: "sql", title: "Running SQL", status: "complete" } as StreamChunk;
    yield "Here are the results..."; // plain text chunks also work
  };
  return { stream: stream(), result: computeResult() };
},

Error handling

If the stream fails mid-way, partial content may already be visible on the platform. The bridge catches the error, scrubs sensitive information, and delivers an error card — either as a follow-up message (mentions and follow-ups) or by editing the thinking indicator (slash commands). The executeQuery callback is not used as a fallback — if streaming fails, the error is reported directly.

Disabling streaming

Set streaming.enabled to false, or omit executeQueryStream entirely. The bridge falls back to the non-streaming executeQuery path, posting the complete result as a single message.

The chunkIntervalMs setting controls how frequently messages are edited on platforms that use the post + edit pattern. Values below 200ms are rejected to avoid platform rate limits. Slack uses native streaming and is not affected by this setting.

Mounted Routes

MethodPathDescription
POST/webhooks/slackSlack webhook (slash commands, events, interactions)
POST/webhooks/teamsTeams webhook (Bot Framework activities)
POST/webhooks/discordDiscord webhook (interactions, gateway events)
POST/webhooks/gchatGoogle Chat webhook (@mentions, DMs, card clicks, Pub/Sub)
POST/webhooks/telegramTelegram webhook (messages, @mentions, commands, callback queries)
POST/webhooks/githubGitHub webhook (issue comments, PR comments, review comments)
POST/webhooks/linearLinear webhook (issue comment events)
GET/webhooks/whatsappWhatsApp webhook verification (Meta challenge-response)
POST/webhooks/whatsappWhatsApp webhook (incoming messages)
GET/oauth/slack/installSlack OAuth install redirect (only when clientId configured)
GET/oauth/slack/callbackSlack OAuth callback (only when clientId configured)

Routes are mounted under the plugin's base path (e.g., /api/plugins/chat-interaction/webhooks/slack).

Environment Variables

VariableDescription
SLACK_BOT_TOKENSlack bot token (xoxb-...)
SLACK_SIGNING_SECRETSlack signing secret
SLACK_CLIENT_IDSlack OAuth client ID (multi-workspace)
SLACK_CLIENT_SECRETSlack OAuth client secret (multi-workspace)
TEAMS_APP_IDMicrosoft App ID
TEAMS_APP_PASSWORDMicrosoft App Password
TEAMS_TENANT_IDMicrosoft Entra ID tenant ID (optional)
DISCORD_BOT_TOKENDiscord bot token
DISCORD_APPLICATION_IDDiscord application ID
DISCORD_PUBLIC_KEYDiscord application public key (64-char hex, Ed25519)
GOOGLE_CHAT_CREDENTIALSGoogle Chat service account key JSON (stringified)
GOOGLE_CHAT_USE_ADCSet to "true" for Application Default Credentials
GOOGLE_CHAT_PUBSUB_TOPICPub/Sub topic for Workspace Events
GOOGLE_CHAT_IMPERSONATE_USERUser email for domain-wide delegation
TELEGRAM_BOT_TOKENTelegram bot token from BotFather
TELEGRAM_WEBHOOK_SECRET_TOKENOptional webhook secret token for request verification
GITHUB_APP_IDGitHub App ID (for App-based auth)
GITHUB_PRIVATE_KEYGitHub App private key in PEM format (for App-based auth)
GITHUB_INSTALLATION_IDInstallation ID for single-tenant mode (optional)
GITHUB_TOKENPersonal Access Token (for PAT auth)
GITHUB_WEBHOOK_SECRETWebhook secret for HMAC-SHA256 verification
LINEAR_API_KEYLinear personal API key (for API key auth)
LINEAR_ACCESS_TOKENLinear OAuth access token (for OAuth token auth)
LINEAR_CLIENT_IDLinear OAuth application client ID (for OAuth App auth)
LINEAR_CLIENT_SECRETLinear OAuth application client secret (for OAuth App auth)
LINEAR_WEBHOOK_SECRETLinear webhook signing secret for HMAC-SHA256 verification
WHATSAPP_PHONE_NUMBER_IDWhatsApp Business phone number ID
WHATSAPP_ACCESS_TOKENSystem User access token for Cloud API
WHATSAPP_VERIFY_TOKENVerify token for webhook challenge-response
WHATSAPP_APP_SECRETMeta App Secret for HMAC-SHA256 signature verification
DATABASE_URLRequired for pg state backend

Error Scrubbing

All error messages are scrubbed before sending to chat platforms. Built-in patterns redact:

  • Connection strings — postgres://, postgresql://, mysql://, mongodb://, redis://, clickhouse://
  • Stack traces — at Module.foo (/path:line:col) patterns
  • File paths — /home/..., /usr/..., /app/..., /src/...
  • API keys and tokens — sk-*, xoxb-*, xoxp-* (Slack), ghp_*, gho_* (GitHub), Bearer *

Provide a custom scrubError callback for additional scrubbing.

Ephemeral Messages

Error messages and rate-limit notices are posted as ephemeral messages by default — visible only to the requesting user, not the entire channel.

PlatformBehavior
SlackNative ephemeral (disappears on reload)
Google ChatNative private message (persists, only target user sees it)
DiscordDM fallback (bot sends a DM to the user)
TeamsDM fallback
TelegramDM fallback
WhatsAppDM fallback
GitHubDM fallback (opens issue/comment thread with user)
LinearDM fallback

Configure via the ephemeral option:

chatPlugin({
  adapters: { slack: { /* ... */ } },
  executeQuery,
  ephemeral: {
    // Default: true — errors shown only to requesting user
    errorsAsEphemeral: true,
  },
})

Set errorsAsEphemeral: false to post errors publicly in the thread.

Proactive Direct Messages

The bridge exposes a sendDirectMessage() method for programmatic DMs — useful for digest delivery, alert notifications, and anomaly detection.

The sendDirectMessage() method is available on the ChatBridge instance returned by the bridge's internal initialization. It accepts a platform name, user ID, and message:

// Send a proactive DM to a Slack user
const result = await bridge.sendDirectMessage(
  "slack",        // platform name
  "U0123456789",  // platform-specific user ID
  "Your saved query detected an anomaly in revenue data.",
);

if (result) {
  console.log(`DM sent: ${result.messageId}`);
}

sendDirectMessage() works across all configured adapters that support DMs. Returns null if the adapter is not configured, does not support DMs, or if delivery fails.

Migrating from @useatlas/slack

// Before:
import { slackPlugin } from "@useatlas/slack";
slackPlugin({ signingSecret: "...", botToken: "xoxb-...", executeQuery })

// After:
import { chatPlugin } from "@useatlas/chat";
chatPlugin({
  adapters: {
    slack: { botToken: "xoxb-...", signingSecret: "..." },
  },
  executeQuery,
})

Update your Slack app webhook URLs:

Route@useatlas/slack@useatlas/chat
Slash commands/commands/webhooks/slack
Events/events/webhooks/slack
Interactions/interactions/webhooks/slack
OAuth install/install/oauth/slack/install
OAuth callback/callback/oauth/slack/callback

Migrating from @useatlas/teams

// Before:
import { teamsPlugin } from "@useatlas/teams";
teamsPlugin({ appId: "...", appPassword: "...", executeQuery })

// After:
import { chatPlugin } from "@useatlas/chat";
chatPlugin({
  adapters: {
    teams: { appId: "...", appPassword: "..." },
  },
  executeQuery,
})

Update the messaging endpoint in your Azure Bot Configuration:

https://your-atlas-api.example.com/api/plugins/chat-interaction/webhooks/teams
Feature@useatlas/teams@useatlas/chat
Webhook/messages/webhooks/teams
Adaptive CardsHand-rolled buildersAutomatic via Chat SDK
StateNoneState adapter (memory/PG/Redis)
Follow-upsSingle query per activityChat SDK subscription model
Multi-platformTeams onlySlack, Teams, Discord, etc.

Troubleshooting

Slack signature verification failures

Ensure SLACK_SIGNING_SECRET matches the signing secret from your Slack app's Basic Information page. Clock skew between your server and Slack can also cause verification failures.

Teams authorization failures

Ensure TEAMS_APP_ID and TEAMS_APP_PASSWORD match the Azure Bot registration credentials. If using tenant restriction, verify the tenant ID matches the Microsoft Entra ID tenant where Teams is configured.

State adapter connection errors

For the pg backend, verify DATABASE_URL is set and the database is accessible. The adapter will fail at plugin initialization if it cannot connect. The redis backend is not yet implemented and will throw at startup — use pg or memory instead.

Plugin returns 503

Routes return 503 if the plugin hasn't finished initializing. This is normal during startup — wait for the Chat interaction plugin initialized log message before sending webhooks.

Reference

On this page