Claude API Telegram Bot Integration Guide
Building a Telegram bot powered by Claude means connecting two pieces: Telegram's Bot API (which handles messages, chats, and updates) and Claude's API (which generates the responses). This guide walks through both sides — registering a bot, setting up a webhook or polling loop, calling Claude with the user's message, and sending the reply back — so you have a working integration by the end.
If you're searching for how to wire these two APIs together, the short answer is: your server receives a Telegram update, extracts the text, sends it to Claude's messages endpoint, and posts the response back via Telegram's sendMessage call. Everything below fills in the details, including the parts people usually get wrong — handling streaming, managing conversation history per chat, and avoiding Telegram's timeout limits.
What you need before starting
- A Telegram bot token from @BotFather (send
/newbotand follow the prompts) - A way to call Claude's API — either Anthropic's SDK directly, or a proxy like SubToAPI that gives you a standard
sub_live_...key and an HTTPS endpoint without managing OAuth or model-specific auth headers - A server endpoint reachable over HTTPS for webhooks, or a long-running process if you prefer polling
Step 1: Register and configure the bot
After creating the bot with BotFather, save the token. You'll use it to call Telegram's Bot API at:
https://api.telegram.org/bot<YOUR_TOKEN>/
Decide between webhooks (Telegram pushes updates to your server) and polling (your server pulls updates on an interval). Webhooks are better for production — lower latency, no wasted requests — but require a public HTTPS URL. Polling is simpler for local testing.
To set a webhook:
curl -X POST "https://api.telegram.org/bot<YOUR_TOKEN>/setWebhook" \
-d "url=https://yourdomain.com/telegram/webhook"
Step 2: Receive and parse incoming messages
Telegram sends a JSON payload to your webhook for every update. The relevant fields are usually message.chat.id and message.text:
app.post('/telegram/webhook', async (req, res) => {
const update = req.body;
const chatId = update.message?.chat?.id;
const text = update.message?.text;
if (!chatId || !text) {
return res.sendStatus(200);
}
const reply = await askClaude(chatId, text);
await sendTelegramMessage(chatId, reply);
res.sendStatus(200);
});
Always return a 200 quickly — Telegram will retry if your webhook doesn't respond within a few seconds, which can cause duplicate messages if Claude's response takes a while to generate.
Step 3: Call Claude for a response
This is where you send the user's message to Claude and get text back. Using SubToAPI, the request looks like a standard HTTP call with your sub_live_... key:
async function askClaude(chatId, text) {
const res = await fetch('https://api.subtoapi.app/v1/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SUBTOAPI_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
messages: conversationHistory[chatId] || [{ role: 'user', content: text }]
})
});
const data = await res.json();
return data.content[0].text;
}
Because Telegram bots are typically consumer-facing and used across teams or multiple users, routing requests through SubToAPI means you get one API key per application, usage metadata per request, and the ability to add team members on your dashboard without sharing raw provider credentials. See the quickstart and messages endpoint docs for the full request format.
Step 4: Send the reply back to Telegram
async function sendTelegramMessage(chatId, text) {
await fetch(`https://api.telegram.org/bot<YOUR_TOKEN>/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chatId, text })
});
}
Telegram messages have a 4096-character limit. If Claude's response is longer, split it into chunks before sending, or instruct Claude in your system prompt to keep answers concise for chat.
Managing conversation history per chat
Telegram bots are stateless by default — each webhook call is independent. To hold a real conversation, you need to store message history keyed by chat.id, typically in Redis or a database:
conversationHistory[chatId] = conversationHistory[chatId] || [];
conversationHistory[chatId].push({ role: 'user', content: text });
// after getting Claude's reply:
conversationHistory[chatId].push({ role: 'assistant', content: reply });
Trim old messages periodically to control token usage and cost — Claude charges per token, and long-running chats can grow expensive fast without a cap.
Handling streaming responses
Telegram doesn't support token-by-token streaming the way a web chat UI does, but you can simulate progressive updates by editing the sent message as new chunks arrive, using editMessageText. This is more complex and usually unnecessary for a bot — most Telegram bot users are fine waiting a few seconds for a complete reply. If you do want streaming for a web-based companion interface, see the streaming docs.
Adding tool use for richer bots
If your bot needs to look up data, call external APIs, or perform actions (checking order status, searching a database), Claude's tool use feature lets you define functions the model can call mid-conversation. The model returns a tool call, your bot executes it, and you feed the result back in a follow-up message. Details are in the tools documentation.
Deployment checklist
- Use HTTPS for your webhook (Telegram requires it)
- Rate-limit by chat ID to avoid one user burning your entire budget
- Log request/response pairs for debugging — Claude's output quality depends heavily on prompt structure
- Set a conservative
max_tokensto control latency and cost - Monitor usage from your pricing plan to make sure you're not exceeding seat limits if multiple team members manage the bot
A production-ready version adds error handling for Telegram API failures, retries for Claude timeouts, and a fallback message when something breaks. Start simple, get the loop working end-to-end, then layer in robustness.
Questions
Do I need Anthropic's official SDK to build this? No. Any HTTP client works since both Telegram's Bot API and Claude's API are plain REST endpoints. The SDK just saves boilerplate.
Can one bot serve multiple Telegram users without mixing up conversations? Yes, as long as you key conversation history by chat.id and don't share a single history array across all users.
Is a webhook or polling better for a Claude-powered bot? Webhooks for production — they're lower latency and don't poll Telegram's servers constantly. Use polling only for local development where you don't have a public HTTPS endpoint yet.