Zernio
Zernio
ResourcesIntegrationsChat SDKn8nMakeZapierOpenClaw
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Integrations

Chat SDK

Build one chatbot that answers Instagram, Facebook, Telegram, WhatsApp, X, Bluesky and Reddit conversations through the Zernio adapter for Vercel's Chat SDK.


When you finish this page a Chat SDK bot answers messages from Instagram, Facebook, Telegram, WhatsApp, X (platform value twitter), Bluesky and Reddit through one adapter and one webhook endpoint. You need an API key with read-write permission, a connected account and Node.js 20+. Zernio holds the platform app registrations and tokens, so the bot never talks to Meta, X, Reddit or Telegram directly.

@zernio/chat-sdk-adapter is the official Zernio adapter, listed on chat-sdk.dev. Every account includes the inbox; messages the bot sends are metered after the first 10,000 each month (pricing). On a legacy AppSumo plan the inbox stays off until support enables it.

Step 1: Install and configure

Install the adapter

npm install @zernio/chat-sdk-adapter chat @chat-adapter/state-memory

@chat-adapter/state-memory keeps state in memory. In production use a persistent state adapter such as @chat-adapter/state-redis or @chat-adapter/state-pg (state adapters).

Set the environment variables

ZERNIO_API_KEY=$ZERNIO_API_KEY
ZERNIO_WEBHOOK_SECRET=$ZERNIO_WEBHOOK_SECRET

ZERNIO_WEBHOOK_SECRET is the secret you set on the webhook endpoint in Step 3; the adapter verifies every delivery's signature with it.

Step 2: Create the bot and its route

Create the bot

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

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

// /.*/ matches every message
bot.onNewMessage(/.*/, async (thread, message) => {
  const platform = (message.raw as any).platform;
  await thread.post(`Hello from ${platform}`);
});

Add the route that receives events

app/api/chat-webhook/route.ts
import { bot } from "@/lib/bot";

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

Step 3: Subscribe Zernio to your route

Create the endpoint in the webhooks dashboard, or call POST /v1/webhooks/settings with name, url, events and the same secret you put in ZERNIO_WEBHOOK_SECRET. Subscribe to message.received and comment.received; add reaction.received if the bot handles reactions, which route to bot.onReaction.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: created } = await zernio.webhooks.createWebhookSettings({
  body: {
    name: 'Chat SDK bot',
    url: 'https://your-app.com/api/chat-webhook',
    events: ['message.received', 'comment.received', 'reaction.received'],
    secret: process.env.ZERNIO_WEBHOOK_SECRET
  }
});

console.log(created.webhook._id);

Response (200):

{
  "success": true,
  "webhook": {
    "_id": "66d9f1a2b3c4d5e6f7a8b9c0",
    "name": "Chat SDK bot",
    "url": "https://your-app.com/api/chat-webhook",
    "events": ["message.received", "comment.received", "reaction.received"],
    "isActive": true,
    "failureCount": 0
  }
}

Send the connected account a DM and the bot replies. The adapter reads its settings from the environment; to pass them explicitly:

const adapter = createZernioAdapter({
  apiKey: process.env.ZERNIO_API_KEY,
  webhookSecret: process.env.ZERNIO_WEBHOOK_SECRET,
  baseUrl: "https://zernio.com/api",  // default
  botName: "My Bot",                   // default: "Zernio Bot"
});
Environment variableConfig keyRequiredDescription
ZERNIO_API_KEYapiKeyYesAPI key used to send messages
ZERNIO_WEBHOOK_SECRETwebhookSecretRecommendedHMAC-SHA256 secret for webhook verification
ZERNIO_API_BASE_URLbaseUrlNoOverride the API base URL
ZERNIO_BOT_NAMEbotNameNoBot display name

Step 4: Read platform data and reactions

Every message carries the raw Zernio payload in message.raw:

bot.onNewMessage(/.*/, async (thread, message) => {
  const raw = message.raw as any;

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

  if (raw.sender.instagramProfile) {
    console.log(raw.sender.instagramProfile.followerCount);
    console.log(raw.sender.instagramProfile.isVerified);
  }

  if (raw.sender.phoneNumber) {
    console.log(raw.sender.phoneNumber); // WhatsApp
  }

  for (const att of raw.attachments) {
    console.log(att.type, att.url);
  }
});

Reactions (WhatsApp, Telegram, Slack, Instagram, Facebook Messenger) arrive as their own event and route to onReaction, never to onNewMessage, so a 👍 is never handled as an inbound message:

bot.onReaction(async (event) => {
  // event.emoji     the normalized emoji (event.rawEmoji is the platform value)
  // event.added     true when added, false when removed
  // event.messageId the message that was reacted to
  // event.thread    the thread where it happened
  if (event.added) {
    await event.thread.post(`Thanks for the ${event.emoji}`);
  }
});

What the adapter supports

FeatureSupportedNotes
Send messagesYesText on every platform
Rich messages (cards)YesButtons and templates on Facebook, Instagram, Telegram, WhatsApp
Edit messagesPartialTelegram only
Delete messagesPartialTelegram, X (full); Bluesky, Reddit (own messages only)
Send reactionsPartialWhatsApp, Telegram, Slack, Instagram, Facebook Messenger
Receive reactions (onReaction)PartialWhatsApp, Telegram, Slack, Instagram, Facebook Messenger, through the reaction.received event
Typing indicatorsPartialFacebook Messenger, Instagram, Telegram, WhatsApp
AI streamingPartialLive post and edit on Telegram; one full reply on platforms without message editing (WhatsApp, Instagram, Facebook, X, Bluesky, Reddit)
File attachmentsYesThrough the media upload endpoint
Fetch messagesYesFull conversation history, with limit, cursor and direction
Fetch thread infoYesParticipant details, platform, status
Webhook verificationYesHMAC-SHA256
Comment webhooksYescomment.received routed through handlers
Reaction webhooksYesreaction.received routed to onReaction

For calls the Chat SDK does not cover, the package exports a client for the Zernio API:

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

const client = new ZernioApiClient(process.env.ZERNIO_API_KEY, "https://zernio.com/api");

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

await client.sendMessage("66c3d2ae7b4f6c8d0e1f2a3b", {
  accountId: "66b2e19d8c3f5a7e9d0b1c2d",
  message: "Here is the menu",
  attachmentUrl: "https://cdn.example.com/menu.jpg",
  attachmentType: "image",
});

If it fails

A 401 with the body Invalid signature from the webhook route means the signature check failed: the secret on the webhook endpoint and ZERNIO_WEBHOOK_SECRET differ, or the endpoint has no secret. Set the same value in both places and redeliver the event from the webhooks dashboard.

Related

  • GitHub repository and the npm package
  • Chat SDK documentation
  • Webhooks
  • Inbox API
Was this page helpful?

Integrations

Create posts from n8n, Make, Zapier or OpenClaw, or build a chatbot with the Chat SDK adapter, using one API key and a connected account.

n8n

Create and schedule posts from an n8n workflow with the verified Zernio node, upload media, and receive post and account events.

On this page

Step 1: Install and configureInstall the adapterSet the environment variablesStep 2: Create the bot and its routeCreate the botAdd the route that receives eventsStep 3: Subscribe Zernio to your routeStep 4: Read platform data and reactionsWhat the adapter supportsIf it failsRelated