Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/docs/adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@
"beta": true,
"readme": "https://github.com/vercel/chat/tree/main/packages/adapter-whatsapp"
},
{
"name": "Zalo",
"slug": "zalo",
"type": "platform",
"icon": "zalo",
"description": "Build bots for Zalo — Vietnam's leading messaging platform — with support for official account messaging and webhooks.",
"packageName": "@chat-adapter/zalo",
"beta": true,
"readme": "https://github.com/vercel/chat/tree/main/packages/adapter-zalo"
},
{
"name": "Redis",
"slug": "redis",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
teams,
telegram,
whatsapp,
zalo,
} from "@/lib/logos";

const iconMap: Record<
Expand All @@ -45,6 +46,7 @@ const iconMap: Record<
postgres,
memory,
whatsapp,
zalo,
instagram: SiInstagram,
signal: SiSignal,
x: SiX,
Expand Down
72 changes: 36 additions & 36 deletions apps/docs/content/docs/adapters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,51 @@ Ready to build your own? Follow the [building](/docs/contributing/building) guid

### Messaging

| Feature | [Slack](/adapters/slack) | [Teams](/adapters/teams) | [Google Chat](/adapters/google-chat) | [Discord](/adapters/discord) | [Telegram](/adapters/telegram) | [GitHub](/adapters/github) | [Linear](/adapters/linear) | [WhatsApp](/adapters/whatsapp) |
|---------|-------|-------|-------------|---------|---------|--------|--------|-----------|
| Post message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Edit message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Delete message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| File uploads | ✅ | ✅ | ❌ | ✅ | ⚠️ Single file | ❌ | ❌ | ✅ Images, audio, docs |
| Streaming | ✅ Native | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ❌ | ❌ | ❌ |
| Scheduled messages | ✅ Native | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Feature | [Slack](/adapters/slack) | [Teams](/adapters/teams) | [Google Chat](/adapters/google-chat) | [Discord](/adapters/discord) | [Telegram](/adapters/telegram) | [GitHub](/adapters/github) | [Linear](/adapters/linear) | [WhatsApp](/adapters/whatsapp) | [Zalo](/adapters/zalo) |
|---------|-------|-------|-------------|---------|---------|--------|--------|-----------|------|
| Post message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Edit message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Delete message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| File uploads | ✅ | ✅ | ❌ | ✅ | ⚠️ Single file | ❌ | ❌ | ✅ Images, audio, docs | ✅ Images |
| Streaming | ✅ Native | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ❌ | ❌ | ❌ | ⚠️ Post only |
| Scheduled messages | ✅ Native | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |

### Rich content

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
| Card format | Block Kit | Adaptive Cards | Google Chat Cards | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown | WhatsApp templates |
| Buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard callbacks | ❌ | ❌ | ✅ Interactive replies |
| Link buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard URLs | ❌ | ❌ | ❌ |
| Select menus | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Tables | ✅ Block Kit | ✅ GFM | ⚠️ ASCII | ✅ GFM | ⚠️ ASCII | ✅ GFM | ✅ GFM | ❌ |
| Fields | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ Template variables |
| Images in cards | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| Modals | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Zalo |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|------|
| Card format | Block Kit | Adaptive Cards | Google Chat Cards | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown | WhatsApp templates | ❌ |
| Buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard callbacks | ❌ | ❌ | ✅ Interactive replies | ❌ |
| Link buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard URLs | ❌ | ❌ | ❌ | ❌ |
| Select menus | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Tables | ✅ Block Kit | ✅ GFM | ⚠️ ASCII | ✅ GFM | ⚠️ ASCII | ✅ GFM | ✅ GFM | ❌ | ❌ |
| Fields | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ Template variables | ❌ |
| Images in cards | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| Modals | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |

### Conversations

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
| Slash commands | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Mentions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Add reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Remove reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ |
| Typing indicator | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| DMs | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Ephemeral messages | ✅ Native | ❌ | ✅ Native | ❌ | ❌ | ❌ | ❌ | ❌ |
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Zalo |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|------|
| Slash commands | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Mentions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Add reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Remove reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ |
| Typing indicator | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| DMs | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| Ephemeral messages | ✅ Native | ❌ | ✅ Native | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |

### Message history

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
| Fetch messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ✅ | ⚠️ Cached sent messages only |
| Fetch single message | ✅ | ❌ | ❌ | ❌ | ⚠️ Cached | ❌ | ❌ | ⚠️ Cached sent messages only |
| Fetch thread info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Fetch channel messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ❌ | ⚠️ Cached sent messages only |
| List threads | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
| Fetch channel info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Post channel message | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Zalo |
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|------|
| Fetch messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ✅ | ⚠️ Cached sent messages only | ❌ |
| Fetch single message | ✅ | ❌ | ❌ | ❌ | ⚠️ Cached | ❌ | ❌ | ⚠️ Cached sent messages only | ❌ |
| Fetch thread info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Fetch channel messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ❌ | ⚠️ Cached sent messages only | ❌ |
| List threads | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Fetch channel info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Post channel message | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |

<Callout type="info">
⚠️ indicates partial support — the feature works with limitations. See individual adapter pages for details.
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A unified SDK for building chat bots across Slack, Microsoft Teams,
type: overview
---

Chat SDK is a TypeScript library for building chat bots that work across multiple platforms with a single codebase. Write your bot logic once and deploy it to Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp.
Chat SDK is a TypeScript library for building chat bots that work across multiple platforms with a single codebase. Write your bot logic once and deploy it to Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, WhatsApp, and Zalo.

## Why Chat SDK?

Expand Down Expand Up @@ -59,6 +59,7 @@ Each adapter factory auto-detects credentials from environment variables (`SLACK
| GitHub | `@chat-adapter/github` | Yes | Yes | No | No | No | No |
| Linear | `@chat-adapter/linear` | Yes | Yes | No | No | No | No |
| WhatsApp | `@chat-adapter/whatsapp` | N/A | Yes | Partial | No | No | Yes |
| Zalo | `@chat-adapter/zalo` | No | No | No | No | Post only | Yes |

## AI coding agent support

Expand All @@ -85,6 +86,7 @@ The SDK is distributed as a set of packages you install based on your needs:
| `@chat-adapter/github` | GitHub Issues adapter |
| `@chat-adapter/linear` | Linear Issues adapter |
| `@chat-adapter/whatsapp` | WhatsApp Business adapter |
| `@chat-adapter/zalo` | Zalo adapter |
| `@chat-adapter/state-redis` | Redis state adapter (production) |
| `@chat-adapter/state-ioredis` | ioredis state adapter (alternative) |
| `@chat-adapter/state-pg` | PostgreSQL state adapter (production) |
Expand Down
51 changes: 51 additions & 0 deletions apps/docs/lib/logos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,54 @@ export const whatsapp = (props: ComponentProps<"svg">) => (
</defs>
</svg>
);

export const zalo = (props: ComponentProps<"svg">) => (
<svg
fill="none"
height="50"
viewBox="0 0 50 50"
width="50"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M22.782 0.166016H27.199C33.2653 0.166016 36.8103 1.05701 39.9572 2.74421C43.1041 4.4314 45.5875 6.89585 47.2557 10.0428C48.9429 13.1897 49.8339 16.7347 49.8339 22.801V27.1991C49.8339 33.2654 48.9429 36.8104 47.2557 39.9573C45.5685 43.1042 43.1041 45.5877 39.9572 47.2559C36.8103 48.9431 33.2653 49.8341 27.199 49.8341H22.8009C16.7346 49.8341 13.1896 48.9431 10.0427 47.2559C6.89583 45.5687 4.41243 43.1042 2.7442 39.9573C1.057 36.8104 0.166016 33.2654 0.166016 27.1991V22.801C0.166016 16.7347 1.057 13.1897 2.7442 10.0428C4.43139 6.89585 6.89583 4.41245 10.0427 2.74421C13.1707 1.05701 16.7346 0.166016 22.782 0.166016Z"
fill="#0068FF"
fillRule="evenodd"
/>
<path
clipRule="evenodd"
d="M49.8336 26.4736V27.1994C49.8336 33.2657 48.9427 36.8107 47.2555 39.9576C45.5683 43.1045 43.1038 45.5879 39.9569 47.2562C36.81 48.9434 33.265 49.8344 27.1987 49.8344H22.8007C17.8369 49.8344 14.5612 49.2378 11.8104 48.0966L7.27539 43.4267L49.8336 26.4736Z"
fill="#001A33"
fillRule="evenodd"
opacity="0.12"
/>
<path
clipRule="evenodd"
d="M7.779 43.5892C10.1019 43.846 13.0061 43.1836 15.0682 42.1825C24.0225 47.1318 38.0197 46.8954 46.4923 41.4732C46.8209 40.9803 47.1279 40.4677 47.4128 39.9363C49.1062 36.7779 50.0004 33.22 50.0004 27.1316V22.7175C50.0004 16.629 49.1062 13.0711 47.4128 9.91273C45.7385 6.75436 43.2461 4.28093 40.0877 2.58758C36.9293 0.894239 33.3714 0 27.283 0H22.8499C17.6644 0 14.2982 0.652754 11.4699 1.89893C11.3153 2.03737 11.1636 2.17818 11.0151 2.32135C2.71734 10.3203 2.08658 27.6593 9.12279 37.0782C9.13064 37.0921 9.13933 37.1061 9.14889 37.1203C10.2334 38.7185 9.18694 41.5154 7.55068 43.1516C7.28431 43.399 7.37944 43.5512 7.779 43.5892Z"
fill="white"
fillRule="evenodd"
/>
<path
d="M20.5632 17H10.8382V19.0853H17.5869L10.9329 27.3317C10.7244 27.635 10.5728 27.9194 10.5728 28.5639V29.0947H19.748C20.203 29.0947 20.5822 28.7156 20.5822 28.2606V27.1421H13.4922L19.748 19.2938C19.8428 19.1801 20.0134 18.9716 20.0893 18.8768L20.1272 18.8199C20.4874 18.2891 20.5632 17.8341 20.5632 17.2844V17Z"
fill="#0068FF"
/>
<path
d="M32.9416 29.0947H34.3255V17H32.2402V28.3933C32.2402 28.7725 32.5435 29.0947 32.9416 29.0947Z"
fill="#0068FF"
/>
<path
d="M25.814 19.6924C23.1979 19.6924 21.0747 21.8156 21.0747 24.4317C21.0747 27.0478 23.1979 29.171 25.814 29.171C28.4301 29.171 30.5533 27.0478 30.5533 24.4317C30.5723 21.8156 28.4491 19.6924 25.814 19.6924ZM25.814 27.2184C24.2785 27.2184 23.0273 25.9672 23.0273 24.4317C23.0273 22.8962 24.2785 21.645 25.814 21.645C27.3495 21.645 28.6007 22.8962 28.6007 24.4317C28.6007 25.9672 27.3685 27.2184 25.814 27.2184Z"
fill="#0068FF"
/>
<path
d="M40.4867 19.6162C37.8516 19.6162 35.7095 21.7584 35.7095 24.3934C35.7095 27.0285 37.8516 29.1707 40.4867 29.1707C43.1217 29.1707 45.2639 27.0285 45.2639 24.3934C45.2639 21.7584 43.1217 19.6162 40.4867 19.6162ZM40.4867 27.2181C38.9322 27.2181 37.681 25.9669 37.681 24.4124C37.681 22.8579 38.9322 21.6067 40.4867 21.6067C42.0412 21.6067 43.2924 22.8579 43.2924 24.4124C43.2924 25.9669 42.0412 27.2181 40.4867 27.2181Z"
fill="#0068FF"
/>
<path
d="M29.4562 29.0944H30.5747V19.957H28.6221V28.2793C28.6221 28.7153 29.0012 29.0944 29.4562 29.0944Z"
fill="#0068FF"
/>
</svg>
);
164 changes: 164 additions & 0 deletions packages/adapter-zalo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# @chat-adapter/zalo

[![npm version](https://img.shields.io/npm/v/@chat-adapter/zalo)](https://www.npmjs.com/package/@chat-adapter/zalo)
[![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/zalo)](https://www.npmjs.com/package/@chat-adapter/zalo)

Zalo Bot adapter for [Chat SDK](https://chat-sdk.dev), using the [Zalo Bot Platform API](https://bot.zapps.me/docs).

## Installation

```bash
pnpm add @chat-adapter/zalo
```

## Usage

```typescript
import { Chat } from "chat";
import { createZaloAdapter } from "@chat-adapter/zalo";

const bot = new Chat({
userName: "mybot",
adapters: {
zalo: createZaloAdapter(),
},
});

bot.onNewMention(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});
```

When using `createZaloAdapter()` without arguments, credentials are auto-detected from environment variables.

## Zalo Bot setup

### 1. Create a Zalo Bot

1. Go to [bot.zapps.me](https://bot.zapps.me) and sign in with your Zalo account
2. Create a new bot and note your **Bot Token** (format: `12345689:abc-xyz`)
3. Go to **Webhooks** settings and set your webhook URL

### 2. Configure webhooks

1. In the Zalo Bot dashboard, navigate to **Webhooks**
2. Set **Webhook URL** to `https://your-domain.com/api/webhooks/zalo`
3. Set a **Secret Token** of your choice (8–256 characters) — this becomes `ZALO_WEBHOOK_SECRET`
4. Subscribe to the message events you need (`message.text.received`, `message.image.received`, etc.)

### 3. Get credentials

From your Zalo Bot dashboard, copy:

- **Bot Token** as `ZALO_BOT_TOKEN`
- The **Secret Token** you set in the webhook config as `ZALO_WEBHOOK_SECRET`

## Configuration

All options are auto-detected from environment variables when not provided.

| Option | Required | Description |
| --------------- | -------- | --------------------------------------------------------------------------------- |
| `botToken` | No\* | Zalo bot token. Auto-detected from `ZALO_BOT_TOKEN` |
| `webhookSecret` | No\* | Secret token for webhook verification. Auto-detected from `ZALO_WEBHOOK_SECRET` |
| `userName` | No | Bot display name. Auto-detected from `ZALO_BOT_USERNAME` (defaults to `zalo-bot`) |
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |

\*Required at runtime — either via config or environment variable.

## Environment variables

```bash
ZALO_BOT_TOKEN=12345689:abc-xyz # Bot token from Zalo Bot dashboard
ZALO_WEBHOOK_SECRET=your-secret # Secret token for X-Bot-Api-Secret-Token verification
ZALO_BOT_USERNAME=mybot # Optional, defaults to "zalo-bot"
```

## Webhook setup

```typescript
// Next.js App Router example
import { bot } from "@/lib/bot";

export async function POST(request: Request) {
return bot.webhooks.zalo(request);
}
```

Zalo delivers all events via POST requests with an `X-Bot-Api-Secret-Token` header. The adapter verifies this header using timing-safe comparison before processing any payload.

## Features

### Messaging

| Feature | Supported |
| -------------- | --------------------------------- |
| Post message | Yes |
| Edit message | No (Zalo limitation) |
| Delete message | No (Zalo limitation) |
| Streaming | Buffered (accumulates then sends) |
| Auto-chunking | Yes (splits at 2000 chars) |

### Conversations

| Feature | Supported |
| ---------------- | ---------------------- |
| Reactions | No (Zalo limitation) |
| Typing indicator | Yes (`sendChatAction`) |
| DMs | Yes |
| Group chats | Pending |
| Open DM | Yes |

### Incoming message types

| Type | Supported |
| ----------------- | ----------------------------- |
| Text | Yes |
| Images | Yes (with optional caption) |
| Stickers | Yes (rendered as `[Sticker]`) |
| Unsupported types | Ignored gracefully |

### Message history

| Feature | Supported |
| ----------------- | ------------------------ |
| Fetch messages | No (Zalo API limitation) |
| Fetch thread info | Yes |

## Thread ID format

```
zalo:{chatId}
```

Example: `zalo:1234567890`

The `chatId` is the conversation ID from the Zalo webhook payload. For group chats it is the group ID; for private chats it is the user ID.

## Notes

- Zalo does not expose message history APIs to bots. `fetchMessages` returns an empty array.
- All formatting (bold, italic, code blocks) is stripped to plain text — Zalo renders no markdown.
- The bot token is embedded in the API URL path and is never logged.
- `isDM()` always returns `true` — Zalo thread IDs do not encode chat type.

## Troubleshooting

### Webhook verification failing

- Confirm `ZALO_WEBHOOK_SECRET` matches the value you entered in the Zalo Bot dashboard
- The adapter compares the `X-Bot-Api-Secret-Token` header using a timing-safe byte comparison — ensure the secret contains only ASCII characters and has no trailing whitespace

### Messages not arriving

- Verify your webhook URL is reachable and returns `200 OK`
- Check that the event types you need are subscribed in the Zalo Bot dashboard

### "Zalo API error" on send

- Confirm `ZALO_BOT_TOKEN` is correct — it should be in `12345689:abc-xyz` format
- The adapter calls `getMe` during `initialize()` to validate the token; check logs for initialization errors

## License

MIT
Loading