By Sarah Chen
Edge Security Engineer
Traditional chatbot hosting is expensive and slow. Running a node process on a VPS 24/7 just to wait for occasional webhook calls from Telegram wastes money and computing resources. In 2026, developers are moving to serverless edge architectures like Cloudflare Workers.
By leveraging V8 runtimes, Cloudflare Workers spin up in milliseconds, handle requests globally, and offer a massive free tier of 100,000 requests per day. This guide is your ultimate blueprint to design, code, and deploy a serverless Telegram bot.
When a user sends a message to your Telegram bot, Telegram forwards it as an HTTP POST request to your webhook. If you use a traditional server, that server must run constantly:
| Metric | VPS Hosting (24/7) | Cloudflare Workers (Edge) |
|---|---|---|
| Monthly Cost | $5.00 - $20.00 | $0.00 (Free Tier) |
| Response Latency | 120ms - 300ms | < 15ms globally |
| Cold Starts | N/A | Near 0ms |
| Scaling | Manual scaling | Automatic to infinity |
| Maintenance | OS updates, security patches | Zero maintenance |
/newbot command.bot (e.g., MyCloudflareBot).123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ). Keep this token completely private.Ensure you have bun installed on your machine, then run:
bun create cloudflare@latest telegram-bot-workerSelect "Hello World" Worker, choose TypeScript, and opt to deploy via Wrangler. Navigate to your project directory:
cd telegram-bot-workerCreate a wrangler.jsonc configuration file to store your variables:
{
"name": "telegram-bot-worker",
"main": "src/index.ts",
"compatibility_date": "2026-06-29",
"compatibility_flags": ["nodejs_compat"],
"vars": {
"TELEGRAM_BOT_TOKEN": "YOUR_SECRET_TOKEN"
}
}Open src/index.ts and write the core serverless router. We will parse the incoming JSON payload from Telegram and route message commands:
export interface Env {
TELEGRAM_BOT_TOKEN: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
try {
const payload: any = await request.json();
if (payload.message) {
await handleMessage(payload.message, env);
}
return new Response("OK", { status: 200 });
} catch (err: any) {
console.error(err);
return new Response(err.message || "Error", { status: 500 });
}
},
};
async function handleMessage(message: any, env: Env) {
const chatId = message.chat.id;
const text = message.text || "";
if (text.startsWith("/start")) {
await sendTelegramMessage(chatId, "Welcome to your Cloudflare serverless bot! Send /help to see commands.", env);
} else if (text.startsWith("/help")) {
await sendTelegramMessage(chatId, "Available commands:
/start - Welcome
/help - Help menu
/time - Current system time", env);
} else if (text.startsWith("/time")) {
await sendTelegramMessage(chatId, `Serverless Time: ${new Date().toISOString()}`, env);
} else {
await sendTelegramMessage(chatId, `You said: "${text}"`, env);
}
}
async function sendTelegramMessage(chatId: number, text: string, env: Env) {
const url = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`;
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
text: text,
parse_mode: "Markdown",
}),
});
}For Telegram to send user messages to your Worker, you must register your Worker's URL. Once you deploy your worker, run the following HTTP request in your terminal:
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" -H "Content-Type: application/json" -d '{"url": "https://telegram-bot-worker.<your-subdomain>.workers.dev"}'Verify that the registration was successful by visiting:
https://api.telegram.org/bot
Standard V8 isolates are stateless. If your bot needs to remember user preferences, api keys, or conversational context, bind a Cloudflare KV Namespace to your worker.
Create a KV namespace:
npx wrangler kv:namespace create BOT_STORAGEAdd the binding to your wrangler.jsonc:
"kv_namespaces": [
{
"binding": "BOT_STORAGE",
"id": "YOUR_KV_NAMESPACE_ID"
}
]Now, read and write data directly within your message routing logic:
// Save user preferences
await env.BOT_STORAGE.put(`user_pref_${chatId}`, JSON.stringify({ theme: "dark" }));
// Read user preferences
const prefs = await env.BOT_STORAGE.get(`user_pref_${chatId}`);Do not paste the token directly into wrangler.jsonc for production. Instead, upload it as a Cloudflare Worker secret:
npx wrangler secret put TELEGRAM_BOT_TOKEN
The secret will automatically become available in your env object at runtime.
Yes, Wrangler resolves standard ESM dependencies during compilation. You can install and import packages like lodash, zod, or marked normally.
Use Cloudflare Queue or ExecutionContext's ctx.waitUntil(promise) to run long tasks asynchronously in the background. This allows your HTTP request to respond instantly back to Telegram within the 10-second webhook limit.