Vercel Connect

Vercel Connect lets your bot use a registered connector for adapter authentication instead of storing long-lived provider secrets. You register a connector once, link it to your project and environments, and your code requests scoped, short-lived tokens at runtime.

The @vercel/connect/chat subpath ships a helper per platform that you spread into the matching create*Adapter factory.

Adapter support

AdapterHelperOutbound field
SlackconnectSlackAdapterbotToken
Microsoft TeamsconnectTeamsAdapterappId, token
GitHubconnectGitHubAdapterinstallationToken
LinearconnectLinearAdapteraccessToken
DiscordconnectDiscordAdapterbotToken, applicationId
NotionconnectNotionAdaptertoken
TelegramconnectTelegramAdapterbotToken

Every helper wires outbound credentials. Helpers for trigger-capable providers also wire inbound traffic:

  • Outbound, when your bot calls the provider API: the helper sets a function-form token field that resolves a fresh, short-lived token per call via getToken.
  • Inbound, when the provider calls your bot: trigger-capable helpers include a webhookVerifier that validates the Vercel OIDC token Connect attaches to trigger-forwarded webhooks. It replaces the provider's native signature check.

Each helper accepts (connector, params?, options?), where params is the getToken parameters minus subject, which is pinned to { type: "app" }. Use params to pass supported token parameters such as installationId or validityBufferMs.

Install

pnpm add @vercel/connect

@vercel/connect reads the deployment's OIDC token automatically. For local development, run vercel link followed by vercel env pull to download a short-lived token into .env.local.

Scaffold a new bot

To start a new project with Connect already wired, pass --connect to create-chat-sdk. The generated bot reads each connector UID from an environment variable such as SLACK_CONNECTOR:

pnpm create chat-sdk@latest my-bot --adapter slack memory --connect -y

Set up a connector

Create a connector in the Vercel dashboard or with the Vercel CLI, then attach it to your project.

  1. Link your local directory to the Vercel project:
vercel link
  1. Create the connector and attach it to the linked project. For Slack, Microsoft Teams, GitHub, Linear, and Discord, enable trigger forwarding and register your Chat SDK webhook route (/api/webhooks/{platform}) as the trigger destination. Notion and Telegram webhooks go directly to your app, so create and attach those connectors without triggers and use native webhook verification or polling.
vercel connect create slack --name acme-slack --triggers
vercel connect attach slack/acme-slack --triggers --trigger-path /api/webhooks/slack

vercel connect attach uses the linked project and enables production, preview, and development by default. Pass --project or --environment to change that. Without --trigger-branch, Connect forwards webhooks to your production deployment.

  1. Pull a development token locally (deployments get VERCEL_OIDC_TOKEN automatically):
vercel env pull

Wire up the adapter

Spread the helper into the adapter factory. The webhook route stays the same, because Connect forwards verified events to the same handler.

Slack

lib/bot.ts
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";
import { connectSlackAdapter } from "@vercel/connect/chat";

export const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter({
      ...connectSlackAdapter("slack/acme-slack"),
    }),
  },
  state: createRedisState(),
});

Replace slack/acme-slack with your connector UID from the Connect dashboard or vercel connect list. Omit signingSecret and SLACK_SIGNING_SECRET when using the helper. The OIDC webhookVerifier is the freshness boundary instead.

Microsoft Teams

lib/bot.ts
import { createTeamsAdapter } from "@chat-adapter/teams";
import { connectTeamsAdapter } from "@vercel/connect/chat";

createTeamsAdapter({
  ...connectTeamsAdapter("microsoft-teams/acme-teams"),
});

The helper resolves the bot's appId during initialization, requests separate Bot Framework and Microsoft Graph tokens through token, and supplies OIDC verification for forwarded webhooks. Enable trigger forwarding to /api/webhooks/teams. Omit TEAMS_APP_ID and TEAMS_APP_PASSWORD when using the helper.

Teams excludes scopes from helper parameters: its token callback requests the scope required by each API call.

Graph reads require the appropriate installation and consent permissions. Managed tokens use the bot's home tenant; the adapter's token callback tenant argument does not change that selection. Call await bot.initialize() before using adapter methods directly. Chat initializes automatically before processing webhooks.

GitHub

lib/bot.ts
import { createGitHubAdapter } from "@chat-adapter/github";
import { connectGitHubAdapter } from "@vercel/connect/chat";

createGitHubAdapter({
  ...connectGitHubAdapter("github/acme-github"),
  userName: "my-bot[bot]",
  botUserId: 12345678, // Numeric ID of your GitHub App bot user
});

installationToken is the installation access token a GitHub App would normally mint via its private-key JWT exchange. The adapter uses it directly and skips that exchange.

Set botUserId to your GitHub App bot user's numeric ID, or set GITHUB_BOT_USER_ID. An installation token cannot identify the bot through /app, and learning the ID from a posted comment only lasts for that process. Configuring it up front lets every serverless instance recognize its own comments and avoid self-reply loops. See the GitHub adapter setup for how to find the ID.

Linear

lib/bot.ts
import { createLinearAdapter } from "@chat-adapter/linear";
import { connectLinearAdapter } from "@vercel/connect/chat";

createLinearAdapter({
  ...connectLinearAdapter("linear/acme-linear"),
  mode: "agent-sessions",
});

We recommend mode: "agent-sessions" for Linear Connect bots. Enable Agent session events on the Linear app and use an app-actor installation. Projects generated with create-chat-sdk --adapter linear --connect include this mode explicitly. The adapter still defaults to "comments" when mode is omitted, so existing bots keep their behavior. For outbound calls outside webhook handling (cron jobs, workflows), wrap them in withInstallation() so a request-scoped client is bound:

lib/jobs.ts
await linear.withInstallation("org-id", async () => {
  await linear.postMessage("linear:issue-id", "Hello from a background job");
});

Discord

lib/bot.ts
import { createDiscordAdapter } from "@chat-adapter/discord";
import { connectDiscordAdapter } from "@vercel/connect/chat";

createDiscordAdapter({
  ...connectDiscordAdapter("discord/acme-discord"),
});

The helper resolves botToken and applicationId from one Connect token response. Omit DISCORD_BOT_TOKEN, DISCORD_PUBLIC_KEY, and DISCORD_APPLICATION_ID. Connect OIDC verification replaces Discord's Ed25519 public-key check for trigger-forwarded interactions.

Notion

lib/bot.ts
import { createNotionAdapter } from "@chat-adapter/notion";
import { connectNotionAdapter } from "@vercel/connect/chat";

createNotionAdapter({
  ...connectNotionAdapter("notion/acme-notion"),
  verificationToken: process.env.NOTION_VERIFICATION_TOKEN,
});

connectNotionAdapter supplies only the outbound token; it does not include a webhookVerifier. Connect does not forward Notion triggers, so configure the webhook subscription directly in Notion and retain NOTION_VERIFICATION_TOKEN for native HMAC verification. Omit NOTION_TOKEN when using the helper.

Telegram

lib/bot.ts
import { createTelegramAdapter } from "@chat-adapter/telegram";
import { connectTelegramAdapter } from "@vercel/connect/chat";

createTelegramAdapter({
  ...connectTelegramAdapter("telegram/acme-telegram"),
  secretToken: process.env.TELEGRAM_WEBHOOK_SECRET_TOKEN,
});

connectTelegramAdapter supplies only the outbound botToken; it does not include a webhookVerifier. The adapter derives webhook deduplication scope from Telegram's stable bot identity, not the rotating credential. Retain TELEGRAM_WEBHOOK_SECRET_TOKEN for native webhook verification or use polling mode. Omit TELEGRAM_BOT_TOKEN when using the helper.

Custom webhook verification

Each trigger-capable helper attaches a default verifier that matches the deployment's project and environment. projectId defaults to VERCEL_PROJECT_ID, and environment defaults to VERCEL_TARGET_ENV, then VERCEL_ENV. As a result, production, preview, and development each accept only their own tokens. Verification fails closed: if those values are absent, every request is rejected. The issuer must be https://oidc.vercel.com or a Vercel team issuer beneath https://oidc.vercel.com/.

To add constraints, such as accepting multiple environments, build a verifier with createConnectWebhookVerifier and override the field:

lib/bot.ts
import {
  connectSlackAdapter,
  createConnectWebhookVerifier,
} from "@vercel/connect/chat";

createSlackAdapter({
  ...connectSlackAdapter("slack/acme-slack"),
  webhookVerifier: createConnectWebhookVerifier({
    environment: ["production", "preview"],
  }),
});

Avoid hardcoding environment: "production" unless you only forward to production. That verifier rejects preview and development deployments.

Notes and limitations

  • The helpers use app-scoped tokens and act as the application itself (subject: { type: "app" }). End-user OAuth is a separate concern.
  • OIDC verification replaces each provider's native signature and timestamp check. Request freshness relies on the short-lived OIDC token's expiry rather than a signed timestamp, and there is no built-in delivery de-duplication, so keep your webhook handlers idempotent.
  • Connect trigger forwarding is HTTP-only, so it doesn't work with the Slack adapter's Socket Mode.
  • Connect forwards to deployed URLs, not localhost. Test against a preview or development deployment.

Read more