Skip to content

Multi-Channel Support

FlowKit supports deploying your conversational flows across multiple messaging platforms with platform-specific formatting.

Overview

Channel adapters handle:

  • Message formatting for each platform
  • Rich content (buttons, images, cards)
  • Platform-specific features
  • Sending/receiving messages

Available Adapters

AdapterImport NameFeatures
WebWebChannelAdapterButtons, images, cards
WhatsAppWhatsAppAdapterButtons, images, typing
TelegramTelegramAdapterInline keyboards, images
SlackSlackAdapterBlock Kit, buttons

Basic Usage

typescript
import {
    ChannelManager,
    WebChannelAdapter,
    WhatsAppAdapter,
    TelegramAdapter,
    SlackAdapter,
} from "@andresaya/flowkit";

ChannelManager

The ChannelManager coordinates multiple channel adapters:

typescript
import { ChannelManager, WhatsAppAdapter, TelegramAdapter } from "@andresaya/flowkit";

const manager = new ChannelManager();

// Register adapters
manager.register(new WhatsAppAdapter({
    accessToken: process.env.WHATSAPP_TOKEN!,
    phoneNumberId: process.env.WHATSAPP_PHONE_ID!,
}));

manager.register(new TelegramAdapter({
    botToken: process.env.TELEGRAM_BOT_TOKEN!,
}));

// Set message handler
manager.onMessage(async (message) => {
    const result = await engine.handle(message.senderId, message.text);
    return { text: result.message };
});

// Handle incoming webhooks
app.post("/webhook/:channel", async (req, res) => {
    const channel = req.params.channel as ChannelType;
    const result = await manager.handleWebhook(channel, req.body);
    res.json(result);
});

WebChannelAdapter

For web-based chat widgets and custom frontends.

typescript
import { WebChannelAdapter } from "@andresaya/flowkit";

const web = new WebChannelAdapter();

// Parse incoming message
const message = web.parseMessage({
    id: "msg-123",
    senderId: "user-456",
    text: "Hello!",
});

// Format response
const formatted = web.formatResponse({
    text: "How can I help you?",
    buttons: [
        { type: "postback", title: "Sales", payload: "sales" },
        { type: "postback", title: "Support", payload: "support" },
    ],
});

Socket.io Integration

typescript
import { Server } from "socket.io";
import { WebChannelAdapter, FlowEngine } from "@andresaya/flowkit";

const io = new Server(server);
const web = new WebChannelAdapter();

io.on("connection", (socket) => {
    const sessionId = `web:${socket.id}`;
    
    // Start conversation
    engine.start(sessionId).then((result) => {
        socket.emit("message", web.formatResponse({ text: result.message }));
    });
    
    // Handle messages
    socket.on("message", async (text: string) => {
        const result = await engine.handle(sessionId, text);
        socket.emit("message", web.formatResponse({ text: result.message }));
    });
});

WhatsAppAdapter

Integration with WhatsApp Business API (Cloud API).

typescript
import { WhatsAppAdapter } from "@andresaya/flowkit";

const whatsapp = new WhatsAppAdapter({
    accessToken: process.env.WHATSAPP_TOKEN!,
    phoneNumberId: process.env.WHATSAPP_PHONE_ID!,
    verifyToken: process.env.WHATSAPP_VERIFY_TOKEN!,  // For webhook verification
});

// Send a message
await whatsapp.send("+1234567890", {
    text: "Hello! How can I help you?",
    buttons: [
        { type: "postback", title: "📦 Track Order", payload: "track" },
        { type: "postback", title: "💬 Support", payload: "support" },
    ],
});

WhatsApp Webhook

typescript
import express from "express";

const app = express();

// Webhook verification (GET)
app.get("/webhook/whatsapp", (req, res) => {
    const result = whatsapp.verifyWebhook(req.query);
    if (result?.valid) {
        res.send(result.challenge);
    } else {
        res.sendStatus(403);
    }
});

// Handle messages (POST)
app.post("/webhook/whatsapp", express.json(), async (req, res) => {
    const message = whatsapp.parseMessage(req.body);
    
    if (message) {
        const result = await engine.handle(
            whatsapp.getConversationId(message),
            message.text
        );
        await whatsapp.send(message.senderId, { text: result.message });
    }
    
    res.sendStatus(200);
});

TelegramAdapter

Integration with Telegram Bot API.

typescript
import { TelegramAdapter } from "@andresaya/flowkit";

const telegram = new TelegramAdapter({
    botToken: process.env.TELEGRAM_BOT_TOKEN!,
});

// Send a message with buttons
await telegram.send("123456789", {
    text: "Welcome! What would you like to do?",
    buttons: [
        { type: "postback", title: "🛒 Shop", payload: "/shop" },
        { type: "url", title: "🌐 Website", url: "https://example.com" },
    ],
});

Telegram Webhook

typescript
app.post("/webhook/telegram", express.json(), async (req, res) => {
    const message = telegram.parseMessage(req.body);
    
    if (message) {
        const result = await engine.handle(
            telegram.getConversationId(message),
            message.text
        );
        await telegram.send(message.senderId, { text: result.message });
    }
    
    res.sendStatus(200);
});

SlackAdapter

Integration with Slack Apps.

typescript
import { SlackAdapter } from "@andresaya/flowkit";

const slack = new SlackAdapter({
    botToken: process.env.SLACK_BOT_TOKEN!,
    signingSecret: process.env.SLACK_SIGNING_SECRET!,
});

// Send a message
await slack.send("U1234567890", {
    text: "Hi! I'm your support assistant.",
    buttons: [
        { type: "postback", title: "Create Ticket", payload: "ticket" },
        { type: "postback", title: "FAQ", payload: "faq" },
    ],
});

Slack Events

typescript
app.post("/webhook/slack", express.json(), async (req, res) => {
    // URL verification
    if (req.body.type === "url_verification") {
        return res.json({ challenge: req.body.challenge });
    }
    
    const message = slack.parseMessage(req.body);
    
    if (message) {
        const result = await engine.handle(
            slack.getConversationId(message),
            message.text
        );
        await slack.send(message.senderId, { text: result.message });
    }
    
    res.sendStatus(200);
});

ResponseBuilder

Fluent API for building responses:

typescript
import { response } from "@andresaya/flowkit";

const msg = response()
    .text("Choose an option:")
    .button("Option A", "a")
    .button("Option B", "b")
    .quickReply("Help", "help")
    .build();

await adapter.send(userId, msg);

Channel Response Format

typescript
interface ChannelResponse {
    text?: string;
    quickReplies?: QuickReply[];
    buttons?: Button[];
    images?: string[];
    cards?: Card[];
    typingDelay?: number;
}

interface Button {
    type: "postback" | "url" | "call";
    title: string;
    payload?: string;
    url?: string;
    phone?: string;
}

interface QuickReply {
    title: string;
    payload: string;
}

interface Card {
    title: string;
    subtitle?: string;
    imageUrl?: string;
    buttons?: Button[];
}

Custom Channel Adapter

Create adapters for other platforms:

typescript
import type { ChannelAdapter, ChannelMessage, ChannelResponse, ChannelType } from "@andresaya/flowkit";

class DiscordAdapter implements ChannelAdapter {
    channel: ChannelType = "custom";
    
    constructor(private botToken: string) {}
    
    parseMessage(payload: any): ChannelMessage | null {
        if (!payload?.content) return null;
        return {
            id: payload.id,
            channel: "custom",
            senderId: payload.author.id,
            text: payload.content,
            timestamp: Date.now(),
        };
    }
    
    formatResponse(response: ChannelResponse): any {
        return { content: response.text };
    }
    
    async send(recipientId: string, response: ChannelResponse): Promise<void> {
        const formatted = this.formatResponse(response);
        // Send via Discord API
    }
    
    getConversationId(message: ChannelMessage): string {
        return `discord:${message.senderId}`;
    }
}

Tips

  1. Prefix session IDs - Include channel: whatsapp:+1234567890
  2. Handle rate limits - Each platform has different limits
  3. Test formatting - Rich content renders differently per platform
  4. Graceful fallback - Fall back to plain text if rich content fails

Released under the MIT License.