---
title: Liveblocks
description: Chat SDK adapter backed by Liveblocks Comments. Build bots that read and post in Liveblocks comment threads using the Chat SDK Channel/Thread/Message model.
tagline: Chat SDK adapter backed by Liveblocks Comments. Maps Liveblocks rooms, threads, and comments to the Chat SDK Channel/Thread/Message model.
package: @liveblocks/chat-sdk-adapter
---

# Liveblocks



## Install

<PackageInstall package="@liveblocks/chat-sdk-adapter" />

## Quick start

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import {
  createLiveblocksAdapter,
  type LiveblocksAdapter,
} from "@liveblocks/chat-sdk-adapter";

const bot = new Chat<{ liveblocks: LiveblocksAdapter }>({
  userName: "MyBot",
  adapters: {
    liveblocks: createLiveblocksAdapter({
      apiKey: process.env.LIVEBLOCKS_SECRET_KEY,
      webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET,
      botUserId: "my-bot-user",
      botUserName: "MyBot",
    }),
  },
  state: createMemoryState(),
});

bot.onNewMention(async (thread, message) => {
  await thread.adapter.addReaction(thread.id, message.id, "👀");
  await thread.post(`Hello, ${message.author.userName}!`);
});
```

The adapter maps Liveblocks rooms to Chat SDK channels, comment threads to Chat SDK threads, and individual comments to messages — so the rest of the Chat SDK API (subscriptions, handlers, posts, reactions) works exactly the same as with any other adapter.

## Configuration

<TypeTable
  type={{
  apiKey: {
    type: "string",
    description: "Liveblocks secret key (`sk_...`) for REST API calls.",
  },
  webhookSecret: {
    type: "string",
    description:
      "Webhook signing secret (`whsec_...`) from the Liveblocks dashboard.",
  },
  botUserId: {
    type: "string",
    description:
      "User ID used when the bot creates, edits, or reacts to comments. Must match your app's user identifiers.",
  },
  botUserName: {
    type: "string",
    default: '"liveblocks-bot"',
    description: "Display name for the bot.",
  },
  resolveUsers: {
    type: "(args: { userIds: string[] }) => Promise<UserInfo[]>",
    description:
      "Resolves user IDs for @mentions. Return one entry per input id in order, or `undefined` to skip.",
  },
  resolveGroupsInfo: {
    type: "(args: { groupIds: string[] }) => Promise<GroupInfo[]>",
    description:
      "Resolves group IDs for @mentions. Same ordering rules as `resolveUsers`.",
  },
  logger: {
    type: "Logger",
    default: 'ConsoleLogger("info")',
    description: "Chat SDK–compatible logger.",
  },
}}
/>

Resolver return types follow the `@liveblocks/core` user and group metadata shapes (`U["info"]`, `DGI`).

### Resolving mentions

When comments contain @mentions, supply `resolveUsers` (and optionally `resolveGroupsInfo`):

```typescript title="lib/bot.ts" lineNumbers
const adapter = createLiveblocksAdapter({
  apiKey: process.env.LIVEBLOCKS_SECRET_KEY,
  webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET,
  botUserId: "my-bot-user",

  resolveUsers: async ({ userIds }) => {
    const users = await getUsersFromDatabase(userIds);
    return users.map((user) => ({
      name: user.fullName,
      avatar: user.avatarUrl,
    }));
  },

  resolveGroupsInfo: async ({ groupIds }) => {
    const groups = await getGroupsFromDatabase(groupIds);
    return groups.map((group) => ({ name: group.displayName }));
  },
});
```

## Platform setup

1. Create a [Liveblocks project](https://liveblocks.io/docs/get-started) with rooms using [Comments](https://liveblocks.io/docs/products/comments).
2. In the dashboard, copy a **secret key** (`sk_...`) for server-side REST API calls.
3. Create a **webhook signing secret** (`whsec_...`) and configure webhooks to subscribe to:
   * `commentCreated`
   * `commentReactionAdded`
   * `commentReactionRemoved`
4. Choose a stable `botUserId` consistent with how your app identifies users — either a real user ID in your system or a dedicated bot ID you issue.

Point your Liveblocks webhook URL at the route that forwards requests to `bot.webhooks.liveblocks` (see [Webhook events](#webhook-events)).

## Webhook events

Supported Liveblocks webhook types:

| Event                    | Role                               |
| ------------------------ | ---------------------------------- |
| `commentCreated`         | Drives Chat SDK message processing |
| `commentReactionAdded`   | Drives reaction handlers           |
| `commentReactionRemoved` | Drives reaction handlers           |

```typescript title="app/api/webhooks/liveblocks/route.ts" lineNumbers
import { after } from "next/server";
import { bot } from "@/lib/bot";

export async function POST(request: Request) {
  return bot.webhooks.liveblocks(request, {
    waitUntil: (task) => after(() => task),
  });
}
```

The adapter verifies signatures with `webhookSecret`; invalid requests return **401**. Passing `waitUntil` lets work continue after the response is sent in serverless environments like Vercel.

## ID encoding

* **Thread ID:** `liveblocks:{roomId}:{threadId}`
* **Channel ID:** `liveblocks:{roomId}`

### `encodeThreadId`

```typescript
adapter.encodeThreadId(data: { roomId: string; threadId: string }): string;
```

```typescript
const encoded = adapter.encodeThreadId({
  roomId: "my-room",
  threadId: "th_abc123",
});
// "liveblocks:my-room:th_abc123"
```

### `decodeThreadId`

```typescript
adapter.decodeThreadId(threadId: string): { roomId: string; threadId: string };
```

Throws if the format is invalid. Room IDs may contain `:` — the **last** `:` separates the `threadId`, so Liveblocks thread IDs themselves must not contain `:`.

## Message format

Liveblocks Comments use a simpler body model than full Markdown. Outbound content from the Chat SDK is converted automatically and some structure is flattened.

**Supported:** paragraphs with bold, italic, code, strikethrough, links, and @mentions (users and groups).

**Flattened to plain text / paragraphs:** headings, bullet and numbered lists, code blocks, tables (rendered as ASCII inside a paragraph), raw HTML. Card payloads become markdown/plain text (or `fallbackText`); interactivity is not preserved.

## Reactions

Use Unicode emoji directly, or names like `thumbs_up` which normalize to emoji where supported. Unknown custom IDs can fail API validation:

```typescript
await thread.adapter.addReaction(thread.id, message.id, "👍");
await thread.adapter.addReaction(thread.id, message.id, "thumbs_up");
```

## Examples

* [Liveblocks Chat SDK Bot](https://liveblocks.io/examples/chat-sdk-bot/nextjs-chat-sdk-bot) — Next.js bot that responds to @mentions and reactions in Liveblocks comment threads ([source](https://github.com/liveblocks/liveblocks/tree/main/examples/nextjs-chat-sdk-bot)).
* [Liveblocks Chat SDK AI Bot](https://liveblocks.io/examples/chat-sdk-ai-bot/nextjs-chat-sdk-ai-bot) — same stack with an AI-powered reply flow ([source](https://github.com/liveblocks/liveblocks/tree/main/examples/nextjs-chat-sdk-ai-bot)).
* Full walkthrough: [Get started with a Chat SDK bot using Liveblocks and Next.js](https://liveblocks.io/docs/get-started/nextjs-chat-sdk-bot).

## Feature support

<FeatureSupport />

## Resources

* [How to build an agent for Liveblocks with Chat SDK and AI SDK](https://vercel.com/kb/guide/liveblocks-chat-sdk-ai-sdk?utm_source=chat-sdk_site\&utm_medium=docs\&utm_campaign=adapter-liveblocks\&utm_content=liveblocks-chat-sdk-ai-sdk) — Build an AI agent that replies to @-mentions in Liveblocks comment threads with streamed responses and tool calling. Uses Chat SDK, the Liveblocks adapter, AI SDK's ToolLoopAgent, and Redis for thread subscriptions and distributed locking.

See all guides and templates on the [resources](/resources?utm_source=chat-sdk_site\&utm_medium=docs\&utm_campaign=adapter-liveblocks\&utm_content=resources) page.
