> For the complete documentation index, see [llms.txt](https://tools.docs.sendbird.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tools.docs.sendbird.com/integrations/vercel-connect.md).

# Vercel Connect

Vercel Connect stores your Sendbird secondary API token for your Vercel projects. This token is a server key for your Sendbird app. Your server code gets the token from Vercel Connect when it runs. It uses the token to call the Sendbird Platform API, the server API for managing your Sendbird app. For example, it issues a session token for each signed-in user. A session token is a short-lived token that lets one user connect to Sendbird Chat. Your app's browser code receives only that token, so the secondary API token never leaves your server.

The Sendbird service in Vercel Connect supports **API key** connections. You create a Sendbird connector, the record in Vercel Connect that holds the secondary API token for one Sendbird app. There's no separate Sendbird sign-in or approval step.

This guide adds Vercel Connect and Chat UIKit to a Next.js app that already has sign-in. Chat UIKit provides ready-made chat screens for React. If you build your own screens instead, you use the Chat SDK, the library that Chat UIKit is built on. To test the chat, you create two test accounts in your app, Alice and Bob, and they send each other their first messages. These are accounts that sign in to your app, not Sendbird users. The example creates the matching Sendbird users when Alice and Bob first select **Start chat**.

## Why use Vercel Connect

Without Vercel Connect, you copy the secondary API token into environment variables. You do this for each Vercel project, each environment, and each developer's local files. With Vercel Connect:

* **The token is stored in one place.** You enter the secondary API token in a Sendbird connector once, and your server code gets it with `getToken()`.
* **You choose where the token can be used.** You link the connector to your Vercel project and choose its environments, such as Development and Preview.
* **Replacing the token doesn't require a new deployment.** You update only the connector. Running servers start using the new token when their cached token expires.
* **The browser never gets the secondary API token.** Your server issues a short-lived session token for each user, and only that token goes to the browser.

Vercel Connect doesn't replace these parts of your app:

* **Token management:** You still issue, replace, and revoke the secondary API token on Sendbird Dashboard.
* **Sign-in:** Your app's own sign-in decides who the user is.
* **Messaging:** Chat UIKit or the Chat SDK connects to Sendbird directly and sends messages. Vercel Connect doesn't pass messages along.

## How it works

1. A signed-in user selects **Start chat** in your app.
2. Your server checks the user's sign-in, then gets the secondary API token from Vercel Connect with `getToken()`.
3. Your server calls the Sendbird Platform API with the secondary API token to issue a session token for that user. Then it returns the session token to the browser.
4. Chat UIKit connects to Sendbird with the session token and sends and receives messages. When the token expires, Chat UIKit asks your server for a new one.

Your server uses the connector only in item 2. The session token is the only credential the browser receives.

## What you can use the connector for

Use the connector for server work that needs the secondary API token. You don't need it where the app's users connect with a session token.

| Use the connector for                                                                                                                                                                                                                                                                                                                                                                                         | You don't need it for                                                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p>• Issuing Chat session tokens for signed-in users, as in this guide<br>• Creating and updating users, channels, and metadata<br>• Sending announcements and system messages, moderating content, and running scheduled cleanup jobs<br>• Sending messages from an AI agent or a bot on your server<br>• Calling the Platform API of Sendbird Calls, the voice and video product, such as listing calls</p> | <p>• Sending and receiving messages with Chat UIKit or the Chat SDK, which use the session token<br>• Initializing the client, which needs only the App ID<br>• Local work with Sendbird CLI, which uses your CLI sign-in</p> |

One connector holds the token for one Sendbird app, because API tokens belong to an app. If you have several Sendbird apps, create a connector for each.

## Credentials and API addresses

| Value               | Where it's kept                                                                                   | What it does                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Secondary API token | In Vercel Connect. Your server gets it.                                                           | Authenticates Chat and Calls Platform API requests in the `Api-Token` header.            |
| Connector UID       | An environment variable on your server, such as `sendbird/my-chat-dev`                            | Selects your Sendbird connector in Vercel Connect. It isn't a secret.                    |
| App ID              | An environment variable on your server. Your server's session route also sends it to the browser. | Identifies the Sendbird app. It isn't a secret.                                          |
| Session token       | Issued by your server to the signed-in user                                                       | Authenticates that user to Chat UIKit or the Chat SDK. It isn't the secondary API token. |

Your server builds the API address from the App ID:

| Product            | Base URL                                             |
| ------------------ | ---------------------------------------------------- |
| Chat Platform API  | `https://api-{application_id}.sendbird.com/v3`       |
| Calls Platform API | `https://api-{application_id}.calls.sendbird.com/v1` |

Vercel Connect lists the Sendbird service with the URL `https://api.sendbird.com`. That URL only identifies the service. Send your API requests to your app's address above.

The secondary API token and the App ID must belong to the same Sendbird app, so change them together when you switch apps. Don't rely on Sendbird to reject a mismatched App ID. Sendbird doesn't check it for every app, so the request can read or change data in the app that owns the token, not the app in your App ID. See [Prepare to use the API](https://sendbird.com/docs/chat/platform-api/v3/prepare-to-use-api).

## Before you begin

<table><thead><tr><th width="260">What you need</th><th>Details</th></tr></thead><tbody><tr><td>A Next.js app with sign-in</td><td>App Router, TypeScript, React 19, and the Node.js runtime. Your server must be able to read the signed-in user's ID. The example uses Auth.js <code>auth()</code>. If you use another sign-in library, replace the <code>auth()</code> call in <code>app/api/chat-session/route.ts</code>. This guide doesn't set up sign-in. Sign-in must also work at your Vercel Preview URLs, because Step 5 tests there.</td></tr><tr><td>A Sendbird app for development</td><td>You need the app's App ID, and permission to manage application settings on Sendbird Dashboard so that you can issue an API token. The app must require a token when users connect. See the notes below this table.</td></tr><tr><td>Sendbird CLI</td><td>Installed and logged in, with your development app selected, so that you can set the app's token policy, which the CLI shows as **Token policy** and stores as `guest_user_policy`. See Steps 1 to 3 of the <a href="/readme/quickstart.md">Quickstart</a>.</td></tr><tr><td>Two test accounts in your app</td><td>Alice and Bob, two accounts that can sign in to your app. Create them the way your app usually adds users. You don't need to create them in Sendbird, because the example creates the matching Sendbird users. Their user IDs in your app become their Sendbird user IDs, so use IDs that never change, such as your app's internal user IDs. Avoid personal information such as email addresses.</td></tr><tr><td>A Vercel project</td><td>A project for development, permission to use Vercel Connect in the team that owns the project, and the Vercel CLI, which you run with <code>npx vercel@latest</code>.</td></tr></tbody></table>

**Token requirement:** Set the development app's token policy to `2`, **Token required**, which blocks connections without a token. The values `0` and `1` let users connect without a token. Clients that already connect to this app without a token stop working after the change, so check for them first. In CLI examples, the `prod` profile points to Sendbird's production environment, and your development app is in that environment too. This isn't related to Vercel's Production environment. With your development app selected in Sendbird CLI, check the value with `sendbird --profile prod app auth-policy get`, and set it with `sendbird --profile prod app auth-policy set --guest-user-policy 2 --yes`.

**App ID:** On Sendbird Dashboard, select your development app and go to **Settings** > **Application** > **General**. Copy the App ID exactly as shown.

This guide was written with these versions:

| Package               | Version |
| --------------------- | ------- |
| Next.js               | 16.4.3  |
| React                 | 19.3.1  |
| @sendbird/uikit-react | 3.20.0  |
| @sendbird/chat        | 4.22.12 |
| @vercel/connect       | 2.5.6   |

## Step 1. Issue a secondary API token

Generate a secondary API token for your development app:

1. Sign in to [Sendbird Dashboard](https://dashboard.sendbird.com/) and select the app.
2. Go to **Settings** > **Application** > **General**.
3. Under **API tokens** > **Secondary API tokens**, select **Generate**. If you're asked, confirm with your password.
4. Copy the token. Copy the App ID from the same page too.

Keep these in mind:

* **Don't use the master API token.** It can't be revoked, so you couldn't block its access if it leaked. You don't need it for the steps above. If you generate secondary API tokens with the Platform API instead, you need the master API token for `POST /v3/applications/api_tokens`. See [Generate a secondary API token](https://sendbird.com/docs/chat/platform-api/v3/application/managing-api-tokens/generate-a-secondary-api-token).
* **Check the token's permissions and IP allowlist.** The token needs permission for the Platform API requests your server makes. If the token has a list of IP addresses that can use it, the list must allow requests from your Vercel servers.
* **An app can have up to ten secondary API tokens.** Plan your connectors around this limit.
* **Check that the app requires a token.** Its token policy must be `2`, as described in [Before you begin](#before-you-begin).

Keep the token ready for Step 2. Don't put it in your code, environment variables, command arguments, or browser code. The step is done when you have a secondary API token and the App ID of the same development app.

## Step 2. Create a Sendbird connector and link your project

1. Open your team's [Vercel Connect](https://vercel.com/connect), select **Create Connector**, and select **Sendbird**. Use the **API key** connection method, and choose **Shared API key**, so that your server uses one secondary API token for every user. The code in Step 3 requests this token with `subject: { type: 'app' }`.
2. Name the connector, for example `my-chat-dev`, enter the secondary API token from Step 1, and save it. Your code refers to the connector by its UID: `sendbird/` followed by the name, such as `sendbird/my-chat-dev`. Copy the exact UID shown for your connector.
3. Link your Vercel project to the connector for the Development and Preview environments. In your project root, run:

   ```bash
   npx vercel@latest link
   npx vercel@latest connect attach sendbird/my-chat-dev -e development -e preview
   ```

   Replace `sendbird/my-chat-dev` with your connector UID. If the Vercel CLI asks you to sign in, follow the prompts. If `vercel link` asks whether to download environment variables, skip it. You download them in item 5. This is the Vercel CLI, not Sendbird CLI. Always pass `-e`. Without it, `attach` links Production, Preview, and Development. Don't link Production, because your production Sendbird app needs its own connector. See [Before you go to production](#before-you-go-to-production).
4. Add the App ID and the connector UID as environment variables for Development and Preview. Neither value is a secret.

   ```bash
   npx vercel@latest env add NEXT_PUBLIC_SENDBIRD_APP_ID
   npx vercel@latest env add SENDBIRD_CONNECTOR
   ```

   Enter your App ID for `NEXT_PUBLIC_SENDBIRD_APP_ID`, and your connector UID, such as `sendbird/my-chat-dev`, for `SENDBIRD_CONNECTOR`. When the CLI asks which environments to add each value to, select only Development and Preview.
5. To run your app locally, download the Development environment to a file:

   ```bash
   npx vercel@latest env pull .env.local --environment development
   ```

   The file holds the two values and a short-lived `VERCEL_OIDC_TOKEN`, which lets your local server get the secondary API token from Vercel Connect. It isn't the secondary API token itself, and it expires. If the `/api/chat-session` log later shows `step: 'connect'`, run the command again and restart `npm run dev`. See [Troubleshooting](#troubleshooting). If you already have `.env.local`, the command updates the downloaded keys and keeps your local-only entries. Don't commit the file to Git.

Create a separate connector for each Sendbird app, such as your development and production apps. Choosing Vercel environments doesn't create separate Sendbird credentials.

The step is done when the connector exists, it's linked to Development and Preview, and `.env.local` has `NEXT_PUBLIC_SENDBIRD_APP_ID` and `SENDBIRD_CONNECTOR`. See [API key connections](https://vercel.com/connect/api-key) and [Project links](https://vercel.com/docs/connect/concepts/project-links).

## Step 3. Issue session tokens on your server

Install the packages in your project root:

```bash
npm install @sendbird/uikit-react@3.20.0 @sendbird/chat@4.22.12 @vercel/connect@2.5.6 server-only
```

Then add two files in your project root. If your project uses a `src/` folder, add them there. The `@/` imports assume the `@/*` path alias in `tsconfig.json`.

`lib/sendbird.ts` calls the Sendbird Platform API with the token from Vercel Connect. It runs only on the server, and builds the Chat and Calls addresses listed in [Credentials and API addresses](#credentials-and-api-addresses). The `server-only` import makes the build fail if browser code imports this file. If the API request URL on Sendbird Dashboard differs from these addresses, use that URL in `baseUrls`.

```ts
import 'server-only';
import { getToken } from '@vercel/connect';

// Neither value is a secret. The secondary API token stays in Vercel Connect.
const connector = process.env.SENDBIRD_CONNECTOR ?? '';
export const appId = process.env.NEXT_PUBLIC_SENDBIRD_APP_ID ?? '';

// Chat and Calls use different Platform API addresses for the same app.
const baseUrls = {
  chat: `https://api-${appId}.sendbird.com/v3`,
  calls: `https://api-${appId}.calls.sendbird.com/v1`,
};

export async function sendbirdApi(
  product: 'chat' | 'calls',
  path: string,
  init: { method?: string; body?: unknown } = {},
) {
  if (!connector || !appId) {
    throw new Error('Set SENDBIRD_CONNECTOR and NEXT_PUBLIC_SENDBIRD_APP_ID.');
  }
  // Gets the secondary API token from Vercel Connect. The SDK caches it.
  const apiToken = await getToken(connector, { subject: { type: 'app' } });
  const res = await fetch(`${baseUrls[product]}${path}`, {
    method: init.method ?? 'GET',
    headers: { 'Api-Token': apiToken, 'Content-Type': 'application/json; charset=utf8' },
    body: init.body === undefined ? undefined : JSON.stringify(init.body),
    cache: 'no-store',
  });
  const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;
  return { status: res.status, json };
}
```

`app/api/chat-session/route.ts` issues the session token for the signed-in user. For the first test, it also creates the Sendbird user and adds every user to one test channel, `vercel-connect-test`. The session token expires after one hour.

{% hint style="warning" %}
The route takes the user ID only from your app's sign-in. Never take it from the request body. Otherwise, anyone could get a session token for another user.
{% endhint %}

The route reads the user ID from `session.user.id`. Auth.js v5 doesn't put the user ID there by default, so the route returns `401` even when the user is signed in. Add these callbacks to your `NextAuth()` config in `auth.ts`:

```ts
callbacks: {
  jwt({ token, user }) {
    if (user?.id) token.sub = user.id;
    return token;
  },
  session({ session, token }) {
    if (session.user && token.sub) session.user.id = token.sub;
    return session;
  },
},
```

<details>

<summary>app/api/chat-session/route.ts</summary>

```ts
import { auth } from '@/auth'; // Your app's Auth.js setup
import { appId, sendbirdApi } from '@/lib/sendbird';

const ALREADY_EXISTS = 400202;
const TEST_CHANNEL_URL = 'vercel-connect-test';
const SESSION_TTL_MS = 60 * 60 * 1000;

export async function POST() {
  // Take the user ID from your app's sign-in. Never take it from the request.
  const session = await auth();
  const userId = session?.user?.id;
  if (!userId) return Response.json({ error: 'Sign in first.' }, { status: 401 });

  try {
    // 1. Create the Sendbird user. If the user already exists, continue.
    const user = await sendbirdApi('chat', '/users', {
      method: 'POST',
      body: { user_id: userId, nickname: userId, profile_url: '' },
    });
    if (user.status !== 200 && user.json.code !== ALREADY_EXISTS) {
      return failed('create user', user.status);
    }

    // 2. Create the test channel, or invite the user if it already exists.
    const channel = await sendbirdApi('chat', '/group_channels', {
      method: 'POST',
      body: { channel_url: TEST_CHANNEL_URL, user_ids: [userId] },
    });
    if (channel.json.code === ALREADY_EXISTS) {
      const invite = await sendbirdApi('chat', `/group_channels/${TEST_CHANNEL_URL}/invite`, {
        method: 'POST',
        body: { user_ids: [userId] },
      });
      if (invite.status !== 200) return failed('invite', invite.status);
    } else if (channel.status !== 200) {
      return failed('create channel', channel.status);
    }

    // 3. Issue a session token for this user.
    const issued = await sendbirdApi('chat', `/users/${encodeURIComponent(userId)}/token`, {
      method: 'POST',
      body: { expires_at: Date.now() + SESSION_TTL_MS },
    });
    if (issued.status !== 200 || typeof issued.json.token !== 'string') {
      return failed('issue session token', issued.status);
    }

    // Only the session token goes to the browser.
    return Response.json({ appId, userId, channelUrl: TEST_CHANNEL_URL, token: issued.json.token });
  } catch {
    // Vercel Connect or the settings failed. Don't log tokens.
    console.error('Chat session failed', { step: 'connect' });
    return Response.json({ error: 'connect' }, { status: 502 });
  }
}

function failed(step: string, status: number) {
  console.error('Chat session failed', { step, status });
  return Response.json({ error: step }, { status: 502 });
}
```

</details>

## Step 4. Connect Chat UIKit with the session token

Add two files under `app/chat/`, or `src/app/chat/` if your project uses a `src/` folder. The browser asks your server for a session token and passes it to the `SendbirdProvider` component in the `accessToken` prop. When the token expires, Chat UIKit calls `onSessionTokenRequired` in `SessionHandler`. That callback asks your server for a new token. See [Session handlers](https://sendbird.com/docs/chat/uikit/v3/react/essentials/handlers#session-handlers).

<details>

<summary>app/chat/chat.tsx</summary>

```tsx
'use client';

import { useState } from 'react';
import { SessionHandler } from '@sendbird/chat';
import SendbirdProvider from '@sendbird/uikit-react/SendbirdProvider';
import GroupChannel from '@sendbird/uikit-react/GroupChannel';
import '@sendbird/uikit-react/dist/index.css';

type ChatSession = { appId: string; userId: string; channelUrl: string; token: string };

// Your server issues the session token. The browser never sees the API token.
async function fetchSession(): Promise<ChatSession> {
  const res = await fetch('/api/chat-session', { method: 'POST' });
  const json = await res.json();
  if (!res.ok) throw new Error(json.error ?? 'Session unavailable');
  return json;
}

export default function Chat() {
  const [session, setSession] = useState<ChatSession | null>(null);
  const [error, setError] = useState<string | null>(null);

  if (!session) {
    return (
      <>
        <button onClick={() => fetchSession().then(setSession, (e: Error) => setError(e.message))}>
          Start chat
        </button>
        {error && <p role="alert">Chat couldn't connect: {error}</p>}
      </>
    );
  }

  return (
    <div style={{ height: '75dvh' }}>
      <SendbirdProvider
        appId={session.appId}
        userId={session.userId}
        accessToken={session.token}
        configureSession={() =>
          new SessionHandler({
            // When the session token expires, get a new one from your server.
            onSessionTokenRequired: (resolve, reject) => {
              fetchSession().then((s) => resolve(s.token), reject);
            },
          })
        }
      >
        <GroupChannel channelUrl={session.channelUrl} />
      </SendbirdProvider>
    </div>
  );
}
```

</details>

<details>

<summary>app/chat/page.tsx</summary>

```tsx
'use client';

import dynamic from 'next/dynamic';

// Chat UIKit needs the browser, so it doesn't render on the server.
const Chat = dynamic(() => import('./chat'), { ssr: false, loading: () => <p>Loading…</p> });

export default function ChatPage() {
  return <Chat />;
}
```

</details>

If you build your own screens with the Chat SDK instead, use the same route. In browser code, initialize the Chat SDK with the App ID from the response, then connect with the session token:

```ts
import SendbirdChat from '@sendbird/chat';
import { GroupChannelModule } from '@sendbird/chat/groupChannel';

const res = await fetch('/api/chat-session', { method: 'POST' });
const session = await res.json();
const sb = SendbirdChat.init({ appId: session.appId, modules: [new GroupChannelModule()] });
await sb.connect(session.userId, session.token);
```

To learn more, see [Authentication](https://sendbird.com/docs/chat/sdk/v4/javascript/application/authenticating-a-user/authentication).

## Step 5. Send your first messages

Start the development server:

```bash
npm run dev
```

Then test the chat with two users:

1. In your browser, sign in to your app as Alice and open `/chat`, for example `http://localhost:3000/chat`. Use the port that the terminal shows.
2. Select **Start chat**. The test channel opens.
3. In a separate browser profile or a private window, sign in to your app as Bob, open `/chat`, and select **Start chat**. Sign in to your app, not to Sendbird Dashboard.
4. As Alice, send `Hello Bob`. Check that the message appears in Bob's window, then reply as Bob.
5. Reload both windows and select **Start chat** again. The messages are still there.
6. Deploy a Preview build with `npx vercel@latest deploy`. If this is the project's first deployment, Vercel deploys it to Production even if you pass `--environment preview`. Production isn't linked to the connector, so chat doesn't work there. Run the command again, and use the Preview URL from the second deployment.
7. At the Preview URL, repeat items 1 to 5. Your app's sign-in must also work there. If Vercel deployment protection is on for Preview, the second browser window shows a Vercel sign-in page first. Sign in to Vercel in both windows, or change the protection setting for Preview.

<table><thead><tr><th width="200">Check</th><th>It worked when</th></tr></thead><tbody><tr><td>Session token</td><td>In your browser's developer tools, the Network tab shows the <code>/api/chat-session</code> response. It contains your App ID, the signed-in user's ID, and a session token, and no secondary API token.</td></tr><tr><td>Messages</td><td>Both users see each other's messages in the same channel. Getting a session token doesn't prove that messaging works.</td></tr><tr><td>Access</td><td>Selecting <strong>Start chat</strong> while signed out shows an error, and <code>/api/chat-session</code> returns status <code>401</code>.</td></tr><tr><td>Token refresh</td><td>Run this check locally. In <code>app/api/chat-session/route.ts</code>, set <code>SESSION_TTL_MS</code> to <code>60 * 1000</code> and keep the chat open for a few minutes. You can still send messages without selecting <strong>Start chat</strong> again. Chat UIKit might not request the new token at exactly one minute. Set the value back to <code>60 * 60 * 1000</code> afterward.</td></tr><tr><td>Preview</td><td>At the Preview URL from item 7, the Session token, Messages, and Access checks pass.</td></tr></tbody></table>

Don't record token values in test notes, screenshots, or logs. A connector that shows as linked in Vercel Connect doesn't prove that chat works.

## Use the connector for the Calls Platform API

The `sendbirdApi()` function in `lib/sendbird.ts`, which you add in Step 3, also calls the Calls Platform API of the same app with the same secondary API token. Pass `'calls'` as the first argument to use the Calls address. For example, this lists direct calls from server code such as a route handler:

```ts
import { sendbirdApi } from '@/lib/sendbird';

const calls = await sendbirdApi('calls', '/direct_calls?limit=1');
```

To learn more about the request, see [Direct call](https://sendbird.com/docs/calls/platform-api/v1/features/direct-call). Before you use Calls APIs, [turn on Calls for the app](/readme/calls/turn-on-calls.md) and check its permissions and plan. A successful API request doesn't prove that a voice or video call works. Authenticating users in the Calls SDK and making a call are separate steps that this guide doesn't cover.

## Before you go to production

* **Use a separate Sendbird app, secondary API token, and connector.** Repeat Step 1 and Step 2 for your production Sendbird app. Then link the Production environment to the new connector, and set the two environment variables for Production:

  ```bash
  npx vercel@latest connect attach sendbird/my-chat-prod -e production
  npx vercel@latest env add NEXT_PUBLIC_SENDBIRD_APP_ID production
  npx vercel@latest env add SENDBIRD_CONNECTOR production
  ```

  Replace `sendbird/my-chat-prod` with your production connector UID.
* **Require tokens in the production app.** Set its token policy to `2`, **Token required**, as described in [Before you begin](#before-you-begin).
* **Choose what other users see.** The example uses the user ID as the nickname, which other users can see. Use a display name instead if your user IDs are internal.
* **Replace the test channel.** The example adds every user to one channel. Create channels for the conversations your service needs, and invite only their members.
* **Add protection to the route:** rate limits per user and per IP address, permission checks, and failure tracking.
* **Limit what users can see.** Users of a Sendbird app can find each other in user lists. If your service mixes users from separate organizations, limit which users and channels each organization can access. Hiding items in your UI doesn't limit API access.
* **Check Vercel billing.** Vercel can bill your team for Vercel Connect token requests. The SDK caches tokens, so not every request is billed. See [Vercel pricing](https://vercel.com/pricing).
* **Test production.** After you make the changes above and deploy, repeat the checks in [Step 5](#step-5-send-your-first-messages) at your production URL.

## Replace or revoke the secondary API token

To replace the secondary API token without stopping chat:

1. Issue a new secondary API token on Sendbird Dashboard. See [Step 1](#step-1-issue-a-secondary-api-token).
2. In Vercel Connect, open your Sendbird connector, go to **Settings** > **API keys**, and paste the whole new token. It replaces the previous token in the connector. New server processes use the new token without a new deployment.
3. Check that a Platform API request from your server works with the new token. The SDK caches the token in each server process, so a running process can still use the old one. For this check, temporarily change the `getToken()` call in `lib/sendbird.ts` to skip the cache with the `forceRefresh` option:

   ```ts
   const apiToken = await getToken(connector, { subject: { type: 'app' } }, { forceRefresh: true });
   ```

   Then run your app locally and select **Start chat**. If the chat opens, the new token works. If the log shows `step: 'connect'`, your local `VERCEL_OIDC_TOKEN` may have expired. Run `npx vercel@latest env pull .env.local --environment development` and try again. Change the call back afterward.

   See [Caching](https://vercel.com/docs/connect/ts-sdk-reference#caching).
4. Make sure that running servers no longer use the old token. The SDK keeps a cached token until the expiry time that Vercel Connect gives it. To switch all servers at once, redeploy your app.
5. Revoke the old token on Sendbird Dashboard. Then check again that session tokens are issued and API calls work. See [Revoke a secondary API token](https://sendbird.com/docs/chat/platform-api/v3/application/managing-api-tokens/revoke-a-secondary-api-token).

Vercel Connect doesn't change or renew the token for you. Deleting a connector doesn't revoke the token in Sendbird. Revoking a token might not immediately disconnect users who already have a session token. Signing a user out of your app doesn't revoke an issued session token either. It stays valid until it expires, which is one hour in this example.

## Troubleshooting

The server logs from `/api/chat-session` show the `step` where it failed. When you run locally, the logs appear in your terminal. For a Vercel deployment, check the deployment's logs on Vercel.

| Symptom                                                               | Check                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You can't find the Sendbird service in Vercel Connect.                | Check the selected Vercel team and your Vercel Connect permissions. In your linked project, run `npx vercel@latest connect discover sendbird`. If Sendbird still doesn't appear, contact Sendbird with your Vercel team name and the command's result.                                                                                                                                                                                                                                               |
| The route returns status 401.                                         | Check that `auth()` returns the signed-in user's ID in your app's server session.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| The log shows `step: 'connect'`.                                      | Check `SENDBIRD_CONNECTOR` and `NEXT_PUBLIC_SENDBIRD_APP_ID`, and that the connector is linked to this project and environment. If the App ID is wrong and no API address exists for it, the log also shows this step. If the App ID belongs to another app, the log shows `step: 'create user'` instead. Locally, run `env pull` again, then restart `npm run dev`. The example doesn't log the error message, so add temporary logging if you need the exact cause, such as `Connector not found`. |
| The log shows `step: 'create user'` or another Sendbird step.         | Check the App ID and the secondary API token together, and the token's permissions and IP allowlist. The log shows only the HTTP status. Sendbird's error code is in the response body, which the example doesn't log. For example, a mismatched App ID can return error `400401` when Sendbird checks the App ID. If you log the body temporarily, remove any tokens from the output before you save or share it.                                                                                   |
| The session token is issued, but chat doesn't connect.                | Check that the App ID and user ID match the session token's app and user, and that the app requires a token, as described in [Before you begin](#before-you-begin).                                                                                                                                                                                                                                                                                                                                  |
| Chat stops working after the session token expires.                   | Check that the user is still signed in. Then check the `/api/chat-session` response and the server log from the time of the refresh.                                                                                                                                                                                                                                                                                                                                                                 |
| The page is blank, or server rendering fails.                         | Check that `app/chat/page.tsx` loads Chat UIKit with `dynamic` and `ssr: false`, and that the chat area has a height.                                                                                                                                                                                                                                                                                                                                                                                |
| The secondary API token appears in browser code or network responses. | Send only the user's session token to the browser. Never put the secondary API token in browser code, such as a component marked `'use client'`.                                                                                                                                                                                                                                                                                                                                                     |
