← Blog

Claude API Telegram Bot Integration Tutorial

2026-11-07 · 5 min read · SubToAPI Team

Building a Telegram bot powered by the Claude API

If you want a Telegram bot that answers messages with Claude instead of canned responses, you need three pieces working together: a Telegram bot token from BotFather, a webhook (or long-polling loop) that receives chat updates, and an HTTP call to an API that talks to Claude. This tutorial walks through all three, from zero to a working bot that replies with streamed Claude output inside a Telegram chat.

The fastest path uses Telegram's Bot API for message handling and a standard HTTPS endpoint for the Claude call — no special SDK is required on either side. Below is a complete Node.js example you can deploy to any server or serverless function.

What you need before starting

Step 1: register a webhook with Telegram

Telegram pushes new messages to a URL you register. Set it once:

curl -X POST "https://api.telegram.org/bot<TELEGRAM_TOKEN>/setWebhook" \
  -d "url=https://yourdomain.com/telegram/webhook"

Telegram will POST a JSON payload to that URL every time a user sends your bot a message.

Step 2: handle incoming messages

Set up a minimal Express endpoint that reads the user's text and the chat ID:

import express from "express";

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

app.post("/telegram/webhook", async (req, res) => {
  const message = req.body.message;
  if (!message || !message.text) {
    return res.sendStatus(200);
  }

  const chatId = message.chat.id;
  const userText = message.text;

  const reply = await askClaude(userText);
  await sendTelegramMessage(chatId, reply);

  res.sendStatus(200);
});

app.listen(3000);

Always respond 200 to Telegram quickly — it retries webhooks that time out, which can duplicate replies if your Claude call is slow.

Step 3: call Claude for a reply

Here's the core function, written against SubToAPI's endpoint (sub_live_... keys work exactly like a standard bearer token):

async function askClaude(userText) {
  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 800,
      messages: [{ role: "user", content: userText }],
    }),
  });

  const data = await response.json();
  return data.content[0].text;
}

The request shape matches the Messages API, so if you've used Claude before this will look familiar — see /docs/messages for the full parameter reference.

Step 4: send the reply back to Telegram

async function sendTelegramMessage(chatId, text) {
  await fetch(
    `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ chat_id: chatId, text }),
    }
  );
}

At this point you have a working loop: Telegram message in, Claude reply out. Deploy this behind any public URL and your bot responds to every chat message.

Keeping conversation context

Telegram doesn't track conversation history for you — each webhook call is a fresh message with no memory of what came before. For a bot that holds a real conversation, you need to store prior turns (in Redis, a database, or even an in-memory map keyed by chatId for a quick prototype) and replay them as the messages array on every request:

const history = conversations.get(chatId) || [];
history.push({ role: "user", content: userText });

const data = await askClaudeWithHistory(history);

history.push({ role: "assistant", content: data.content[0].text });
conversations.set(chatId, history.slice(-20)); // cap history length

Trim history regularly — Claude's context window is large but not unlimited, and every stored turn adds to the tokens billed on each request.

Streaming replies for long answers

Telegram doesn't support token-by-token streaming the way a chat UI does, but you can still stream from Claude server-side and edit the Telegram message as chunks arrive, which feels much more responsive than waiting for a full response:

const res = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 800,
    stream: true,
    messages: [{ role: "user", content: userText }],
  }),
});

Buffer the stream, update a placeholder Telegram message every ~500ms with editMessageText, and finalize it when the stream closes. Details on event types and parsing are in /docs/streaming.

Adding tool use for real actions

If your bot needs to do more than chat — look up order status, hit an internal API, query a database — define tools in the request and handle the tool_use blocks Claude returns before sending a final reply to the user. This is standard function-calling behavior; see /docs/tools for the request/response shapes and multi-turn tool loops.

Production considerations

A few things matter once real users are hitting the bot:

If you want the key management and billing side handled without building your own proxy, sign up for a trial at /signup and check /pricing for plan details — the /docs/quickstart page walks through generating your first sub_live_... key.

questions

Do I need Anthropic's SDK to build a Telegram bot with Claude? No. The Messages API is a plain HTTPS JSON endpoint, so fetch or curl from any language works fine — the examples above use no SDK at all.

Can the bot remember previous messages in a Telegram chat? Not automatically. Telegram sends each message independently, so you must store conversation history yourself (per chatId) and resend it as the messages array on each Claude request.

What's the simplest way to test the webhook before deploying? Use ngrok http 3000 to expose your local server, register that URL with setWebhook, and message your bot directly — no deployment needed for initial testing.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →