channel docs

channel / quickstart

Multichannel in four steps.

From zero to a live multichannel thread: push the schema, boot the platforms, mount one webhook, render one component. Sending is a fifth, optional step — inbound conversations work without it.

1 — Install and push the schema

terminal
pnpm add @ekairos/channel @ekairos/agent
npx instant-cli@latest push schema

The channel domain composes the events domain, so pushing agentDomain (which includes channel) gives you everything:

instant.schema.ts
import { agentDomain } from "@ekairos/agent/schema";

export default agentDomain.toInstantSchema();
Already have an app domain? Compose instead: domain("app").includes(agentDomain).withSchema({...}). Your entities and the channel entities live in the same InstantDB app — that is what makes the UI plug & play.

2 — Boot the platforms

lib/channels.ts
import { createChannels } from "@ekairos/channel/platforms";
import { db } from "@/lib/db"; // InstantDB admin client

export const channels = await createChannels({
  db,
  userName: "ekairos",
  platforms: {
    slack: {
      botToken: process.env.SLACK_BOT_TOKEN!,
      signingSecret: process.env.SLACK_SIGNING_SECRET!,
    },
    telegram: { botToken: process.env.TELEGRAM_BOT_TOKEN! },
  },
  resolveContextId: async ({ channel, threadKey }) => {
    // one platform conversation = one agent thread
    const thread = await ensureThread({ key: `${channel}:${threadKey}` });
    return thread.contextId;
  },
  react: async (inbound) => {
    const reaction = await reactOnThread(inbound.contextId, inbound.message);
    return reaction.text; // posted back on the same platform, persisted outbound
  },
});

Each configured platform needs its adapter package installed once (pnpm add @chat-adapter/slack, etc.) — they are optional peers, so you only carry the platforms you use. Credentials and per-platform setup live in Platform setup.

3 — Mount the webhook

app/api/channels/[platform]/route.ts
import { channels } from "@/lib/channels";

export async function POST(
  request: Request,
  { params }: { params: Promise<{ platform: string }> },
) {
  const { platform } = await params;
  const handler = channels.webhooks[platform];
  if (!handler) {
    return new Response("unknown platform", { status: 404 });
  }
  return handler(request);
}

One route serves every platform. Point each platform's webhook URL at /api/channels/<platform> and the inbound pipeline is live: message → canonical record → agent reaction → reply.

4 — Render the timeline

terminal
pnpm dlx shadcn@latest add https://registry.ekairos.dev/r/channel-timeline.json
app/threads/[key]/page.tsx
"use client";

import { db } from "@/lib/db.client"; // InstantDB react client
import { ChannelTimeline } from "@/components/ekairos/channel/channel-timeline";

export function ThreadConversation({ contextId }: { contextId: string }) {
  return <ChannelTimeline db={db} contextId={contextId} />;
}

No fetch, no API route, no props plumbing: the component queries channel_messages reactively. A whatsapp reply appears in the timeline the moment the webhook persists it.

5 — (Optional) Send from your product

Outbound from the UI is the one endpoint you own — see Send messages. Drop in ChannelComposer and point it at that endpoint.

Next

database…