---
title: Velt
description: Chat SDK adapter backed by Velt Comments. Build bots that read and respond in Velt comment threads on documents, text editors, and canvases using the Chat SDK Channel/Thread/Message model.
tagline: Chat SDK adapter backed by Velt Comments. Maps Velt organizations, documents, comment annotations, and comments to the Chat SDK Channel/Thread/Message model.
package: @veltdev/chat-sdk-adapter
---

# Velt



## Install

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

## Quick start

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

const bot = new Chat<{ velt: VeltAdapter }>({
  userName: "Velt Bot",
  adapters: {
    velt: createVeltAdapter({
      apiKey: process.env.VELT_API_KEY,
      webhookSecret: process.env.VELT_WEBHOOK_SECRET,
      botUserId: "velt-bot",
      botUserName: "Velt Bot",
    }),
  },
  state: createMemoryState(),
});

bot.onNewMention(async (thread, message) => {
  await thread.post(`Hi ${message.author.fullName}! How can I help?`);
});
```

The adapter maps Velt documents to Chat SDK channels, comment annotations 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: "Velt API key for REST API calls. Falls back to `VELT_API_KEY`.",
  },
  webhookSecret: {
    type: "string",
    description:
      "Webhook signing secret. Advanced (v2) uses the Svix-style `whsec_...`; Basic (v1) uses the Console auth token. Falls back to `VELT_WEBHOOK_SECRET`.",
  },
  botUserId: {
    type: "string",
    description:
      "User ID the bot posts, edits, and reacts as. Add it to your Velt organization so it is @-mentionable.",
  },
  botUserName: {
    type: "string",
    description: "Display name for the bot, used for @-mention detection.",
  },
  authToken: {
    type: "string",
    description:
      "Velt auth token (`x-velt-auth-token`). If omitted, the adapter generates one for `botUserId` and refreshes it. Falls back to `VELT_AUTH_TOKEN`.",
  },
  organizationId: {
    type: "string",
    description:
      "Default organization id; used to scope generated tokens and as a webhook fallback. Falls back to `VELT_ORGANIZATION_ID`.",
  },
  webhookVersion: {
    type: '"v2" | "v1"',
    default: '"v2"',
    description: "Which Velt webhook system to verify. Advanced (v2) by default.",
  },
  resolveUsers: {
    type: "(args: { userIds: string[] }) => Promise<UserInfo[]>",
    description:
      "Resolves user IDs to display info for @mentions and authors. Return one entry per input id in order, or `undefined` to skip.",
  },
  selfHostingConfig: {
    type: "VeltSelfHostingConfig",
    description:
      "Enables reaction writes (`addReaction` / `removeReaction`) via a self-hosted reactions service. The managed Velt backend has no reaction-write API.",
  },
  logger: {
    type: "Logger",
    description: "Chat SDK–compatible logger. Defaults to a no-op.",
  },
}}
/>

## Platform setup

1. Create a [Velt account](https://console.velt.dev) and copy your **API key**.
2. Add a **bot user** to your organization (`POST /v2/users/add` with `userId: "velt-bot"`) so it can post and appears in the @-mention list.
3. Configure webhooks (Velt Console → **Configurations → Webhook Service**, or `POST /v2/workspace/webhookconfig/update`) and subscribe to:
   * `comment.add`
   * `comment_annotation.add`
   * `comment.reaction_add`
   * `comment.reaction_delete`
4. Copy the **signing secret** (`whsec_...` for Advanced/v2 webhooks) into `webhookSecret`.

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

## Webhook events

| Event                     | Role                               |
| ------------------------- | ---------------------------------- |
| `comment.add`             | Drives Chat SDK message processing |
| `comment_annotation.add`  | First comment of a new thread      |
| `comment.reaction_add`    | Drives reaction handlers           |
| `comment.reaction_delete` | Drives reaction handlers           |

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

export const runtime = "nodejs"; // raw body + crypto; not edge

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

Velt has two webhook systems and the adapter verifies both: **Advanced (v2)** with Svix-style HMAC-SHA256 (`whsec_...`, default) and **Basic (v1)** with an `Authorization: Basic <token>` header (set `webhookVersion: "v1"`). Invalid requests return **401**. Passing `waitUntil` lets work continue after the response is sent in serverless environments like Vercel.

## ID encoding

* **Thread ID:** `velt:{organizationId}:{documentId}:{annotationId}`
* **Channel ID:** `velt:{organizationId}:{documentId}`

### `encodeThreadId`

```typescript
adapter.encodeThreadId(data: {
  organizationId: string;
  documentId: string;
  annotationId: string;
}): string;
```

```typescript
const encoded = adapter.encodeThreadId({
  organizationId: "my-org",
  documentId: "doc-1",
  annotationId: "NHR5sMWU7YmTv2HJ1nVC",
});
// "velt:my-org:doc-1:NHR5sMWU7YmTv2HJ1nVC"
```

### `decodeThreadId`

```typescript
adapter.decodeThreadId(threadId: string): {
  organizationId: string;
  documentId: string;
  annotationId: string;
};
```

Each segment is URL-encoded, so ids may safely contain `:`. Throws if the format is invalid.

## Message format

Velt Comments are stored as `commentHtml`, so the adapter converts between Velt HTML and the Chat SDK's mdast AST (`VeltFormatConverter`). Inbound `{{userId}}` mention tokens are normalized to readable `@Name`. Each parsed `Message.raw` also carries lightweight document context (`documentName`, `documentUrl`, `anchoredText`) so bots can ground replies.

**Supported:** paragraphs with bold, italic, code, strikethrough, links, and @mentions.

**Flattened to plain text / paragraphs:** headings, lists, code blocks, tables, and card payloads (rendered as fallback text). Interactivity is not preserved.

## Reactions

Reading reactions (`onReaction`) works on all Velt plans.

**Writing** reactions (`addReaction` / `removeReaction`) is not supported on the managed Velt backend — there is no managed REST endpoint to add a reaction as a user, so these methods throw a clear `PermissionError`. To enable reaction writes, provide `selfHostingConfig.reactionsService` (a self-hosted reactions service backed by your own database).

## Examples

* [Velt Chat SDK Bot](https://github.com/velt-js/velt-chat-sdk-adapter/tree/main/examples/nextjs-velt-bot) — Next.js bot that replies to @-mentions in Velt comment threads.
* [Velt Chat SDK AI Bot](https://github.com/velt-js/velt-chat-sdk-adapter/tree/main/examples/nextjs-velt-ai-bot) — same stack with a streaming Claude reply flow.
* Full walkthrough: [Get started with a Chat SDK bot using Velt](https://velt.dev/docs/ai/chat-sdk-adapter).

## Feature support

<FeatureSupport />
