Telegram bots are quietly powering millions of automated workflows, from customer support to data pipelines—yet many developers start with bulky frameworks. By skipping libraries like python-telegram-bot and using Telegram’s native HTTP API directly, you gain full control over performance, latency, and feature set without sacrificing simplicity. Here’s how to build a high-speed Telegram bot using pure Python and a single dependency: the requests library.
Why go native? Speed, control, and clarity
While high-level libraries abstract away boilerplate, they also introduce overhead through abstraction layers. Telegram’s Bot API communicates over HTTPS, so native HTTP requests let you:
- Avoid framework startup delays.
- Tune timeouts and retries precisely.
- Integrate seamlessly into existing microservices.
- Stay under Telegram’s strict rate limits without hidden throttling.
For small bots or internal tools, a zero-dependency approach keeps deployment files under 100 KB and startup times under 200 ms. That’s often faster than spinning up a Node.js or Python runtime with heavy packages.
Setting up: three quick prerequisites
Before writing code, collect three essentials:
- A Telegram account to link your bot.
- A bot token from @BotFather (issued instantly via Telegram’s CLI-style interface).
- Python 3.8 or newer with pip and requests installed.
Once you’ve sent /newbot to @BotFather and copied the token, store it securely—never commit it directly to version control. Use environment variables or a local secrets manager instead.
Core workflow: polling vs. webhooks
Telegram offers two update delivery methods: long polling and webhooks. Polling is simpler for local testing; webhooks are mandatory for production workloads.
Long polling: the simplest path
Start with a loop that fetches new messages at 30-second intervals. The following snippet shows how to retrieve updates and extract chat IDs and text:
import requests
import time
TOKEN = "YOUR_BOT_TOKEN"
BASE_URL = f"
def get_updates(offset=None):
url = f"{BASE_URL}/getUpdates"
params = {"offset": offset, "timeout": 30}
response = requests.get(url, params=params)
return response.json().get("result", [])
def send_message(chat_id, text):
url = f"{BASE_URL}/sendMessage"
payload = {"chat_id": chat_id, "text": text}
requests.post(url, json=payload)
def main():
offset = None
while True:
updates = get_updates(offset)
if updates:
for update in updates:
chat_id = update["message"]["chat"]["id"]
text = update["message"].get("text", "")
if text == "/start":
send_message(chat_id, "🚀 Bot is online!")
elif text == "/ping":
send_message(chat_id, "🏓 Pong!")
offset = updates[-1]["update_id"] + 1
time.sleep(1)
if __name__ == "__main__":
main()This loop runs locally and is ideal for prototypes or development servers. It uses long polling, which keeps the connection open for up to 30 seconds and reduces round trips compared to short polling.
Webhooks: instant updates for production
Switching to webhooks eliminates polling entirely. Instead of repeatedly asking Telegram for new messages, Telegram pushes updates directly to your server via HTTPS POST. Here’s how to set it up:
from flask import Flask, request
WEBHOOK_URL = "
# Register once (run in a script or notebook)
requests.get(f"{BASE_URL}/setWebhook?url={WEBHOOK_URL}")
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
update = request.get_json()
chat_id = update["message"]["chat"]["id"]
text = update["message"].get("text", "")
if text == "/start":
send_message(chat_id, "🚀 Bot is online!")
return "OK"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)Remember to register your HTTPS endpoint with Telegram. Tools like Ngrok make this trivial during development:
ngrok http 5000Copy the HTTPS ngrok URL, append /webhook, and pass it to setWebhook. Telegram will verify the endpoint within seconds.
Scaling up: async, queues, and rate limits
As your bot gains traction, three optimizations become critical:
- Async I/O: Replace synchronous requests with
httpxoraiohttpto handle concurrent updates without blocking. - Queues: Use a thread or process to manage outgoing messages and avoid rate limit spikes.
- State: Offload session data to Redis when your bot tracks user progress across multiple updates.
Here’s a minimal async send function:
import httpx
async def async_send_message(chat_id, text):
async with httpx.AsyncClient(timeout=10) as client:
await client.post(
f"{BASE_URL}/sendMessage",
json={"chat_id": chat_id, "text": text}
)For outgoing queues, spawn a daemon thread to drain messages at a steady 25 per second—well below Telegram’s 30-message ceiling:
from queue import Queue
import threading
message_queue = Queue(maxsize=1000)
def message_worker():
while True:
chat_id, text = message_queue.get()
send_message(chat_id, text)
message_queue.task_done()
threading.Thread(target=message_worker, daemon=True).start()Enqueue messages instead of calling send_message directly:
message_queue.put((chat_id, "Your reply here"))Beyond text: keyboards, files, and callbacks
Native bots aren’t limited to plain text. You can attach inline keyboards for quick interactions:
from json import dumps
def send_keyboard(chat_id):
keyboard = {
"inline_keyboard": [[
{"text": "Option 1", "callback_data": "opt1"},
{"text": "Option 2", "callback_data": "opt2"}
]]
}
payload = {
"chat_id": chat_id,
"text": "Choose an option:",
"reply_markup": dumps(keyboard)
}
requests.post(f"{BASE_URL}/sendMessage", json=payload)Handling callback queries requires another endpoint:
@app.route("/callback", methods=["POST"])
def handle_callback():
query = request.get_json()
if query["data"] == "opt1":
send_message(query["from"]["id"], "You chose Option 1!")
return "OK"File uploads follow a similar pattern—stream the file directly in the POST body:
def send_photo(chat_id, photo_path):
with open(photo_path, "rb") as photo:
requests.post(
f"{BASE_URL}/sendPhoto",
files={"photo": photo},
data={"chat_id": chat_id}
)From laptop to cloud: deployment blueprints
Local development is only the first step. For scalable, always-on bots, consider these patterns:
- Serverless: Deploy a Python Flask or FastAPI handler on AWS Lambda with API Gateway, using webhooks as the trigger.
- Containers: Package your bot in a lightweight Docker image and run it on Kubernetes or a cloud VM.
- Edge functions: Use Cloudflare Workers or Deno Deploy to cut cold starts to near-zero.
A minimal AWS Lambda handler:
def lambda_handler(event, context):
from urllib.parse import parse_qs
body = parse_qs(event["body"])
update = json.loads(body["update"][0])
handle_updates([update])
return {"statusCode": 200}Looking ahead: where native shines brightest
Native bots excel in constrained environments—edge devices, serverless platforms, and low-latency pipelines. As your feature set grows, layer on async I/O, Redis for state, and monitoring via getWebhookInfo to stay within Telegram’s policies.
Start small, measure latency with tools like cURL, and scale deliberately. The fastest Telegram bot isn’t the one with the most libraries—it’s the one with the least overhead between user action and bot response.
AI summary
Discover how to create lightning-fast Telegram bots using Python’s requests library and Telegram’s HTTP API for maximum control and minimal latency.
Tags