@ajac-zero/chat-adapter-zulip
v0.1.0
Published
Community Zulip adapter for the Chat SDK (https://chat-sdk.dev)
Downloads
42
Maintainers
Readme
@ajac-zero/chat-adapter-zulip
Community Zulip adapter for the Chat SDK. Connects a Chat SDK bot to a Zulip organization via the outgoing-webhook integration for inbound traffic and the Zulip REST API for outbound traffic.
Status: community-maintained. A merge of this implementation into the official
@chat-adapter/*scope is pending — track the upstream proposal at the Chat SDK repository. Once merged officially, this package will be deprecated in favour of@chat-adapter/zulip.
Installation
pnpm add @ajac-zero/chat-adapter-zulip chatchat is a peer dependency so the bot uses the same Chat instance as every other adapter.
Usage
The adapter auto-detects ZULIP_SITE, ZULIP_BOT_EMAIL, ZULIP_API_KEY, ZULIP_BOT_USERNAME, and ZULIP_WEBHOOK_TOKEN from the environment.
import { Chat } from "chat";
import { createZulipAdapter } from "@ajac-zero/chat-adapter-zulip";
const bot = new Chat({
userName: "mybot",
adapters: {
zulip: createZulipAdapter(),
},
});
bot.onNewMention(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});Webhook route
import { bot } from "@/lib/bot";
export async function POST(request: Request): Promise<Response> {
return bot.webhooks.zulip(request);
}Configure the URL as a Zulip outgoing-webhook bot under Personal settings → Bots → Add a new bot → Outgoing webhook, then point it at https://your-domain.com/api/webhooks/zulip. Copy the bot's webhook token (shown on the bot's settings page — distinct from the bot's API key, even though both look like 32-character tokens) into ZULIP_WEBHOOK_TOKEN.
Configuration
| Option | Required | Description |
|--------|----------|-------------|
| site | Yes | Zulip organization base URL, e.g. https://example.zulipchat.com. Auto-detected from ZULIP_SITE |
| apiKey | Yes | Bot API key. Auto-detected from ZULIP_API_KEY |
| botEmail | No | Bot user email, used as the REST API Basic Auth username. Auto-detected from ZULIP_BOT_EMAIL. Falls back to the value reported in the first webhook payload |
| webhookToken | No | Shared token embedded in outgoing-webhook payloads. Auto-detected from ZULIP_WEBHOOK_TOKEN. Distinct from apiKey — Zulip generates a separate value when you create an outgoing-webhook bot. When unset, incoming requests are accepted without verification (a warning is logged once) |
| userName | No | Bot display name for mention detection. Auto-detected from ZULIP_BOT_USERNAME or /api/v1/users/me |
| logger | No | Logger instance (defaults to ConsoleLogger("info")) |
Environment variables
ZULIP_SITE=https://example.zulipchat.com
[email protected]
ZULIP_API_KEY=...
ZULIP_BOT_USERNAME="My Bot"
ZULIP_WEBHOOK_TOKEN=...Thread IDs
- Stream topics:
zulip:s:{streamId}:{base64UrlTopic} - Direct messages:
zulip:p:{sortedUserIds}
Topics are base64url-encoded because they may contain colons and other delimiter characters.
Features
Messaging
| Feature | Supported | |---------|-----------| | Post message | Yes | | Edit message | Yes | | Delete message | Yes | | File uploads | No | | Streaming | Post+Edit fallback |
Rich content
| Feature | Supported | |---------|-----------| | Card format | Markdown fallback | | Buttons | No | | Link buttons | No | | Select menus | No | | Tables | GFM | | Fields | Yes | | Images in cards | No | | Modals | No |
Conversations
| Feature | Supported | |---------|-----------| | Slash commands | No | | Mentions | Yes (outgoing-webhook trigger) | | Add reactions | Yes | | Remove reactions | Yes | | Reaction events | No (outgoing webhooks only fire on new messages) | | Typing indicator | Yes | | DMs | Yes | | Ephemeral messages | No |
Message history
| Feature | Supported |
|---------|-----------|
| Fetch messages | Yes (REST /api/v1/messages) |
| Fetch single message | No (planned) |
| Fetch thread info | Yes |
| Fetch channel info | Yes (streams only) |
| List threads | No |
| Fetch channel messages | No (planned) |
| Post channel message | Use Thread.post |
Notes
- This adapter intentionally limits its surface to what Zulip's outgoing-webhook integration delivers: new stream messages where the bot is
@-mentioned, and DMs addressed to the bot. Reaction events, message edits, and presence updates are not received via the webhook channel. Layer a Zulip event-queue poller on top if you need them. - Cards render as Zulip markdown using the SDK's standard
cardToFallbackTexthelper. Buttons and select menus have no Zulip equivalent and are dropped from the rendered output. - Bodies are truncated to fit Zulip's 10 000-character per-message limit, with an ellipsis appended.
- Stream sending uses
type: "stream"with the numeric stream id; DMs usetype: "direct"with a JSON-encoded list of recipient ids. - SDK emoji placeholders (
{{emoji:wave}}) are substituted with Unicode emoji before sending so the literal text never leaks into your Zulip channel.
Building from source
pnpm install
pnpm test
pnpm typecheck
pnpm buildLicense
MIT
