Zernio

One adapter, seven platforms. Reach Instagram, Facebook, Twitter/X, Telegram, WhatsApp, Bluesky, and Reddit through Zernio without managing each platform's developer program, app review, or token rotation.

Vendor-official adapter maintained by Zernio, not Vercel or Chat SDK contributors. For feature requests, bug reports, and support, file an issue on the adapter's repo.

Install

pnpm add @zernio/chat-sdk-adapter chat @chat-adapter/state-memory

For production, swap @chat-adapter/state-memory for a persistent state adapter such as @chat-adapter/state-redis or @chat-adapter/state-pg. See State Adapters for all options.

Quick start

lib/bot.ts
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createZernioAdapter } from "@zernio/chat-sdk-adapter";

export const bot = new Chat({
  userName: "pizza-bot",
  adapters: {
    zernio: createZernioAdapter(),
  },
  state: createMemoryState(),
});

// Pattern is a RegExp — `/.*/ ` matches every message.
bot.onNewMessage(/.*/, async (thread, message) => {
  const platform = (message.raw as { platform: string }).platform;
  await thread.post(`Hello from ${platform}!`);
});
app/api/chat-webhook/route.ts
import { bot } from "@/lib/bot";

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

Why Zernio

Even with native Chat SDK adapters for each platform, shipping a multi-platform bot still means applying to Meta's developer program, going through App Review, getting WhatsApp Business verification, applying for X elevated access, and managing token rotation across all of them. Zernio replaces that with a dashboard where users connect their own accounts and you get one API key.

Configuration

Environment variables

VariableRequiredDescription
ZERNIO_API_KEYYesZernio API key for sending messages
ZERNIO_WEBHOOK_SECRETRecommendedHMAC-SHA256 secret for verifying inbound webhooks
ZERNIO_API_BASE_URLNoOverride the API base URL (default: https://zernio.com/api)
ZERNIO_BOT_NAMENoBot display name (default: "Zernio Bot")

Explicit configuration

import { createZernioAdapter } from "@zernio/chat-sdk-adapter";

const adapter = createZernioAdapter({
  apiKey: "your-api-key",
  webhookSecret: "your-webhook-secret",
  baseUrl: "https://zernio.com/api",
  botName: "My Bot",
});

Setup

  1. Get a Zernio API key. Sign up at zernio.com and create an API key from the dashboard with read-write permissions.
  2. Connect social accounts. Use the Zernio dashboard or API to connect the platform accounts you want the bot to handle.
  3. Configure a webhook pointing at your bot's webhook endpoint:
    • URL: https://your-app.com/api/chat-webhook
    • Events: message.received and comment.received
    • Secret: strong shared secret, passed as ZERNIO_WEBHOOK_SECRET
  4. Enable the inbox addon on your Zernio account to receive message webhooks.

How it works

Incoming
  User sends a DM on Instagram/Telegram/etc.
    -> Platform delivers to Zernio
    -> Zernio fires `message.received` webhook
    -> Adapter verifies signature & parses payload
    -> Chat SDK routes through your handlers

Outgoing
  Your handler calls thread.post("Hello!")
    -> Adapter calls the Zernio REST API
    -> Zernio delivers to the correct platform
    -> User receives the message on the originating platform

Thread ID format

Thread IDs follow the format zernio:{accountId}:{conversationId}:

  • accountId — the Zernio social account ID (which platform account received the message)
  • conversationId — the Zernio conversation ID (the specific DM thread)
  • For comments: zernio:{accountId}:comment:{postId}
import { ZernioAdapter } from "@zernio/chat-sdk-adapter";

const adapter = new ZernioAdapter({ apiKey: "..." });
const { accountId, conversationId } = adapter.decodeThreadId(threadId);

Platform support matrix

FeatureFBIGTelegramWhatsAppXBlueskyReddit
Send textYYYYYYY
ButtonsYYYY
TypingYY
DeleteYYSelfSelf
ReactionsYY
MediaYYYYY
EditY

Rich messages

The adapter maps Chat SDK Card elements to native platform formats instead of falling back to plain text:

import { Actions, Button, Card, CardText, LinkButton } from "chat";

await thread.post(
  Card({
    title: "Order #1234",
    subtitle: "Total: $50.00",
    imageUrl: "https://example.com/product.jpg",
    children: [
      CardText("Your order is ready for pickup."),
      Actions([
        Button({ id: "confirm", label: "Confirm", style: "primary" }),
        LinkButton({ label: "Track Order", url: "https://example.com/track" }),
      ]),
    ],
  })
);

Renders as an interactive card on Facebook, Instagram, Telegram, and WhatsApp. Falls back to plain text on X, Bluesky, and Reddit.

AI streaming

Stream AI responses using the post+edit pattern — thread.post() accepts an AsyncIterable<string>, so you can pass the textStream from streamText directly:

import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

bot.onNewMessage(/.*/, async (thread, message) => {
  const result = streamText({
    model: openai("gpt-4o"),
    prompt: message.text,
  });

  // Telegram: posts initial message, edits as tokens arrive.
  // Other platforms: collects the full response, posts once.
  await thread.post(result.textStream);
});

Platform-specific data

Reach platform-specific fields through message.raw:

bot.onNewMessage(/.*/, async (thread, message) => {
  const raw = message.raw as {
    platform: string;
    sender: {
      instagramProfile?: { followerCount: number; isVerified: boolean };
      phoneNumber?: string;
    };
  };

  console.log(raw.platform); // "instagram", "facebook", "telegram", ...

  if (raw.sender.instagramProfile) {
    console.log(`Followers: ${raw.sender.instagramProfile.followerCount}`);
    console.log(`Verified: ${raw.sender.instagramProfile.isVerified}`);
  }

  if (raw.sender.phoneNumber) {
    console.log(`Phone: ${raw.sender.phoneNumber}`);
  }
});

API client

The adapter ships a standalone REST client for direct Zernio API calls:

import { ZernioApiClient } from "@zernio/chat-sdk-adapter";

const client = new ZernioApiClient("your-api-key", "https://zernio.com/api");

const { data, pagination } = await client.listConversations({
  platform: "instagram",
  status: "active",
  limit: 20,
});

const messages = await client.fetchMessages(conversationId, accountId);

await client.sendTyping(conversationId, accountId);
await client.addReaction(conversationId, messageId, accountId, "👍");

const { url } = await client.uploadMedia(fileBuffer, "image/jpeg");

Webhook verification

The adapter automatically verifies webhook signatures when webhookSecret is configured. You can also call the verifier directly:

import { verifyWebhookSignature } from "@zernio/chat-sdk-adapter";

const isValid = verifyWebhookSignature(rawBody, signature, secret);

Error handling

The adapter maps Zernio API errors to standard Chat SDK error classes:

HTTP statusError classDescription
401AuthenticationErrorInvalid or expired API key
403PermissionErrorRead-only key, missing addon, etc.
404ResourceNotFoundErrorConversation or message not found
429AdapterRateLimitErrorRate limit hit (includes retryAfter)
5xxNetworkErrorServer error

Feature support

Messaging

FeatureSupported
Post message
Edit messageTelegram
Delete messageTelegram, X
File uploads
Streamingpost+edit (Telegram)
Scheduled messages

Rich content

FeatureSupported
Card formatFB, IG, Telegram, WhatsApp
ButtonsFB, IG, Telegram, WhatsApp
Link buttonsFB, IG, Telegram, WhatsApp
Select menus
Tables
Fields
Images in cardsFB, IG, Telegram, WhatsApp
Modals

Conversations

FeatureSupported
Slash commands
Mentions
Add reactionsTelegram, WhatsApp
Remove reactionsTelegram, WhatsApp
Typing indicatorFB, Telegram
DMs
Ephemeral messages
User lookup
Parent subject
Native client
Custom API endpointbaseUrl

Message history

FeatureSupported
Fetch messages
Fetch single message
Fetch thread info
Fetch channel messages
List threads
Fetch channel info
Post channel message