GitHub

Build bots that respond to pull request and issue comment threads. Treats issues and PRs as threads, comments as messages.

Create a new bot

Scaffold a Next.js bot with the GitHub adapter using create-chat-sdk:

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

The generated app uses the Memory state adapter for local development. To add the adapter to an existing app, start at Install.

Install

pnpm add @chat-adapter/github @chat-adapter/state-memory

Quick start

The adapter auto-detects credentials from GITHUB_TOKEN (or GITHUB_APP_ID/GITHUB_PRIVATE_KEY), GITHUB_WEBHOOK_SECRET, and GITHUB_BOT_USERNAME.

For managed credentials and webhook verification, see Vercel Connect.

lib/bot.ts
import { Chat } from "chat";
import { createGitHubAdapter } from "@chat-adapter/github";
import { createMemoryState } from "@chat-adapter/state-memory";

export const bot = new Chat({
  userName: "my-bot",
  adapters: {
    github: createGitHubAdapter(),
  },
  state: createMemoryState(),
});

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

The memory state adapter keeps subscriptions and locks in process memory, which suits local development. Use Redis or PostgreSQL in production.

Then create the webhook route:

app/api/webhooks/github/route.ts
import { after } from "next/server";
import { bot } from "@/lib/bot";

export async function POST(request: Request): Promise<Response> {
  return bot.webhooks.github(request, {
    waitUntil: (task) => after(() => task),
  });
}

Authentication

Vercel Connect

Use Vercel Connect to source installation access tokens at runtime instead of storing a GitHub App private key. The connectGitHubAdapter() helper from @vercel/connect/chat wires an installationToken resolver, which skips the App JWT exchange, and a webhookVerifier for Connect trigger-forwarded webhooks:

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

createGitHubAdapter({
  ...connectGitHubAdapter("github/acme-github"),
  userName: "my-bot[bot]",
});

installationToken accepts a string or () => string | Promise<string> resolver invoked per API call. When webhookVerifier is set it takes precedence over webhookSecret and GITHUB_WEBHOOK_SECRET.

In Connect mode the adapter holds only an installation token, so it can't auto-detect its own bot user ID: the /app lookup needs the App's JWT. Without that ID, the adapter can't recognize its own comments and replies to itself in a loop. It learns the ID from the first comment it posts, but only in memory, which isn't enough on serverless where each webhook can hit a fresh instance. Pass botUserId, the numeric ID of your your-app[bot] user, so every instance knows it up front. You can look it up with curl -s 'https://api.github.com/users/your-app%5Bbot%5D'.

createGitHubAdapter({
  ...connectGitHubAdapter("github/acme-github"),
  botUserId: 12345678,
});

Or set the GITHUB_BOT_USER_ID environment variable, which the adapter auto-detects.

Create the connector and attach it to your project with the Vercel CLI. --trigger-path registers the GitHub webhook route as the trigger destination, and vercel env pull downloads a development OIDC token for local runs:

vercel link
vercel connect create github --name acme-github --triggers
vercel connect attach github/acme-github --triggers --trigger-path /api/webhooks/github
vercel env pull

Without --trigger-branch, Connect forwards webhooks to your production deployment. See Vercel Connect for the full setup.

GitHub App

We recommend a GitHub App. It has better rate limits and security than a personal access token and supports multiple installations.

Create the app

  1. Go to Settings then Developer settings then GitHub Apps then New GitHub App.
  2. Set Webhook URL to https://your-domain.com/api/webhooks/github.
  3. Set a Webhook secret.
  4. Under permissions, set Issues and Pull requests to Read & write, and Metadata to Read-only.
  5. Subscribe to events: Issue comment, Pull request review comment.
  6. Click Create GitHub App and generate a private key.

Install the app

  1. From your app's settings click Install App and choose repositories.
  2. Note the Installation ID in the URL: https://github.com/settings/installations/12345678.

Single-tenant configuration

Set installationId to accept comments only from that GitHub App installation. After verification, a new comment (issue_comment or pull_request_review_comment with action created) from a different installation gets a 404, handlers don't run, and the adapter logs a warning with both IDs. The check runs whether you verify with webhookSecret or a custom webhookVerifier, and other event types and actions aren't checked.

Deliveries without an installation object still reach handlers. GitHub omits it from repository and organization webhooks, so those keep working in this mode. Vercel Connect mode (installationToken) has no configured installation ID and skips this check.

createGitHubAdapter({
  appId: process.env.GITHUB_APP_ID!,
  privateKey: process.env.GITHUB_PRIVATE_KEY!,
  installationId: parseInt(process.env.GITHUB_INSTALLATION_ID!, 10),
});

Multi-tenant configuration

Omit installationId:

createGitHubAdapter({
  appId: process.env.GITHUB_APP_ID!,
  privateKey: process.env.GITHUB_PRIVATE_KEY!,
});

The adapter extracts installation IDs from incoming webhooks and caches API clients per installation.

Personal access token

A personal access token suits personal projects, testing, and single-repo bots.

  1. Go to Settings then Developer settings then Personal access tokens.
  2. Create a token with repo scope.
  3. Set GITHUB_TOKEN.
createGitHubAdapter({
  token: process.env.GITHUB_TOKEN!,
});

Configuration

Prop

Type

One of token, appId+privateKey, or installationToken (Vercel Connect) is required, plus either webhookSecret or a webhookVerifier.

Environment variables

VariableRequiredDescription
GITHUB_TOKENOne auth mode requiredPersonal access token.
GITHUB_APP_IDOne auth mode requiredGitHub App ID. Use with GITHUB_PRIVATE_KEY.
GITHUB_PRIVATE_KEYOne auth mode requiredGitHub App private key (PEM). Use with GITHUB_APP_ID.
GITHUB_INSTALLATION_IDNoInstallation ID for a single-tenant GitHub App. Omit for multi-tenant setups.
GITHUB_WEBHOOK_SECRETYes, unless using a webhookVerifierWebhook secret.
GITHUB_BOT_USERNAMENoBot username for @mention detection. Defaults to github-bot.
GITHUB_BOT_USER_IDRecommended with Vercel ConnectNumeric ID of the bot user, so the adapter can recognize its own comments.

Webhooks

For repository or organization webhooks:

  1. Open Settings then Webhooks then Add webhook.
  2. Set the payload URL to https://your-domain.com/api/webhooks/github.
  3. Set Content type to application/json. The default application/x-www-form-urlencoded does not work.
  4. Set Secret to match webhookSecret.
  5. Select events: Issue comments, Pull request review comments.

You configure GitHub App webhooks when you create the app. Make sure to select application/json there too.

Reactions

SDK emojiGitHub reaction
thumbs_up+1
thumbs_down-1
laughlaugh
confusedconfused
heartheart
hoorayhooray
rocketrocket
eyeseyes

Installation lookup

Resolve the GitHub App installation ID associated with a Thread or Message:

bot.onNewMention(async (thread, message) => {
  const installationIdFromThread = await github.getInstallationId(thread);
  const installationIdFromMessage = await github.getInstallationId(
    message.threadId
  );
});

The result depends on the auth mode:

  • With a personal access token, it returns undefined.
  • With a single-tenant GitHub App, it returns the configured installation ID.
  • With a multi-tenant GitHub App, it succeeds only after the adapter has received a webhook for that repository and cached the mapping. Use a persistent state adapter so the mapping survives restarts.

Direct API client

Access the underlying Octokit instance through .octokit:

const github = bot.getAdapter("github").octokit;
const { data: pulls } = await github.rest.pulls.list({
  owner: "vercel",
  repo: "chat",
  state: "open",
});

Personal access token and single-tenant App modes return the same client anywhere. Multi-tenant mode requires webhook handler context, and calling .octokit outside a handler throws.

The previous .client getter still works as a deprecated alias for .octokit.

Thread IDs

Issues and pull requests map to threads, and comments map to messages:

TypeContextThread ID format
PR-levelPR Conversation tabgithub:{owner}/{repo}:{prNumber}
Review commentsPR Files Changed tabgithub:{owner}/{repo}:{prNumber}:rc:{commentId}
Issue commentsIssue threadgithub:{owner}/{repo}:issue:{issueNumber}

Feature support

Messaging

FeatureSupported
Post message
Message replies
Edit message
Delete message
File uploads
StreamingBuffered
Scheduled messages

Rich content

FeatureSupported
Card formatGFM Markdown
Buttons
Link buttons
Select menus
TablesGFM
Charts
Fields
Images in cards
Modals

Conversations

FeatureSupported
Slash commands
Mentions
Add reactions
Remove reactionsPartial
Typing indicator
Mark as read
Message edit events
Message delete events
DMs
Ephemeral messages
User lookup
Parent subject
Native client
Custom API endpoint

Message history

FeatureSupported
Fetch messages
Fetch single message
Fetch thread info
Fetch channel messages
List threads
Fetch channel info
Post channel message

Resources

  • Ship a GitHub code review bot with Hono and Redis: build a GitHub bot that reviews pull requests on demand. When a user @mentions the bot on a PR, Chat SDK picks up the mention, spins up a Vercel Sandbox with the repo cloned, and uses AI SDK to analyze the diff.

See all guides and templates on the resources page.

More adapters