Skip to main content
The @hitheo/telegram package connects Theo to Telegram as a bot. Users message your bot, Theo handles the response with the full orchestration pipeline, and the adapter replies through the Bot API.
Theo never hosts your bot. You create the bot with @BotFather, hold the token, and deploy this adapter on your infrastructure; Theo only processes the completion traffic the adapter forwards. That keeps every message, webhook URL, and credential under your control.

Prerequisites

  1. A Telegram bot token from @BotFather
  2. A Theo API key (theo_sk_...)

Install

npm install @hitheo/telegram

Quick Setup

createTelegramHandler returns an async function that processes a single Telegram webhook update. Wire it into any HTTP framework — Express, Next.js route handlers, Cloudflare Workers, AWS Lambda, etc.
import express from "express";
import { createTelegramHandler } from "@hitheo/telegram";

const app = express();
app.use(express.json());

const handle = createTelegramHandler({
  theoApiKey: process.env.THEO_API_KEY!,
  telegramToken: process.env.TELEGRAM_BOT_TOKEN!,
  secretToken: process.env.TELEGRAM_WEBHOOK_SECRET, // optional but recommended
  mode: "auto",
  skills: ["customer-support"],
  persona: "You are a helpful assistant for Acme Corp.",
});

app.post("/webhook", async (req, res) => {
  try {
    await handle(req.body, {
      secretToken: req.header("X-Telegram-Bot-Api-Secret-Token"),
    });
    res.sendStatus(200);
  } catch (err) {
    // TelegramSignatureError indicates an unauthorized POST.
    res.sendStatus(401);
  }
});

app.listen(3000, () => console.log("Telegram bot listening on :3000"));

How It Works

  1. User sends a message to your Telegram bot
  2. Telegram POSTs the update to your webhook URL
  3. The adapter verifies the X-Telegram-Bot-Api-Secret-Token header (when secretToken is configured)
  4. The adapter forwards the text (and any photo/document attachments) to POST /api/v1/completions, reusing the conversation_id associated with that chat
  5. Theo processes the request through the full pipeline (classification, skills, agent loop)
  6. The response is sent back to the Telegram chat, split into multiple messages if needed

Handler Config

OptionTypeRequiredDescription
theoApiKeystringYour theo_sk_... API key
telegramTokenstringBot token from BotFather
secretTokenstringShared secret passed to setWebhook. When set, updates with a missing or mismatched header throw TelegramSignatureError.
theoBaseUrlstringAPI base URL (default: https://www.hitheo.ai)
modeChatModeDefault mode — auto, fast, think, code, etc.
personaPersonaInputCustom persona or "theo" / "none"
skillsstring[]Skill slugs to activate on every message
conversationStoreConversationStoreMap chat IDs → Theo conversation IDs. Defaults to an in-process Map; swap for Redis/DB across instances.
enableVoicebooleanTranscribe voice/audio with Theo speech-to-text and append the transcript to the prompt. Off by default (STT adds cost).
maxChunkCharsnumberMax chars per outbound message. Default 4000.

Built-in Commands

The adapter handles three commands itself (no Theo call):
CommandBehavior
/startSends an intro message
/helpLists commands
/resetClears the stored conversation_id for this chat so the next message starts a fresh context
Any other /foo command returns a short “Unknown command” reply.

Attachments

Photos attached to messages (and image documents) are downloaded inside the adapter and forwarded to Theo as base64 image data — the adapter never passes a Telegram file URL to Theo. This keeps the bot token inside your deployment.
Always run the latest @hitheo/telegram release. If you are still on a ≤0.1.3 version, upgrade to ^0.1.4 and rotate the bot token via @BotFather as part of the upgrade.
Voice and audio messages are ignored unless you set enableVoice: true. When enabled, the adapter downloads the voice clip, runs it through Theo speech-to-text, and appends the transcript to the prompt as text. Voice is not sent as an attachment — Theo’s completion endpoint only accepts image attachments.

Setting Up the Webhook

Register your webhook URL with Telegram and include the secret_token:
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhook",
    "secret_token": "'"$TELEGRAM_WEBHOOK_SECRET"'"
  }'

Conversation Persistence Across Instances

The default ConversationStore is an in-process Map — fine for a single long-running server, wrong for serverless (each invocation is a fresh process). Provide your own store:
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

const handle = createTelegramHandler({
  theoApiKey: process.env.THEO_API_KEY!,
  telegramToken: process.env.TELEGRAM_BOT_TOKEN!,
  conversationStore: {
    get: (chatId) => redis.get(`tg:conv:${chatId}`),
    set: async (chatId, id) => {
      await redis.set(`tg:conv:${chatId}`, id, "EX", 60 * 60 * 24 * 30);
    },
    clear: async (chatId) => {
      await redis.del(`tg:conv:${chatId}`);
    },
  },
});

Deploying

Deploy as any Node.js HTTP server. For production:
  • Railway / Render / Fly.io — easiest for webhook-based bots
  • Vercel / Cloudflare Workers — serverless (one invocation per message); pair with a remote conversationStore
  • Docker + PM2 — self-hosted
Use theo_sk_test_ keys during development to avoid billing.