Implementar un bot de Telegram en Python y JavaScript con despliegue en AWS
Y desplegando un nuevo bot de Telegram en AWS
Aquí están mis notas con un tutorial paso a paso sobre cómo implementar y desplegar en AWS un bot de Telegram. He incluido un inicio rápido (polling largo) y un camino listo para producción (webhooks), con ejemplos en Python y Node.js.
Los bots de Telegram son aplicaciones que se ejecutan en tus servidores y se comunican con los usuarios a través de Telegram. Vamos a registrar un bot, escribir un pequeño programa y conectarlo a la API de Bots de Telegram.
TL;DR
- Crea un bot a través de @BotFather → obtén el token.
- Construye una pequeña aplicación con python-telegram-bot o Telegraf.
- Comienza con polling largo localmente; cambia a webhooks para producción.
Lo que necesitarás
-
Una cuenta de Telegram (móvil o de escritorio)
-
Terminal básico + editor de código
-
Uno de los siguientes:
- Python 3.10+ y
pip
- Node.js 18+ y
npm
/pnpm
/yarn
- Python 3.10+ y
Crea tu bot con BotFather (obtén el token)
- En Telegram, busca a @BotFather y inicia una conversación.
- Envía
/newbot
y sigue las instrucciones para nombrar tu bot y elegir un nombre de usuario único (debe terminar conbot
), por ejemplo,example_helper_bot
. - BotFather responde con un token de API como
123456:ABC-DEF...
. Trátalo como una contraseña; no lo comites en Git. Puedes regenerarlo siempre a través de BotFather si se filtra.
Consejo: El tutorial oficial de Telegram recorre estos pasos exactos y cómo verificar tu token con
getMe
.
Elige tu pila
Puedes llamar directamente a la API de Bots de HTTP, pero usar una biblioteca mantenida es más rápido.
- Python:
python-telegram-bot
(asincrónico, moderno). ([https://docs.python-telegram-bot.org]) - Node.js:
telegraf
(maduro, amigable con TypeScript). ([https://telegraf.js.org])
La API de Bots en sí misma está documentada aquí y es la fuente de verdad para métodos como getUpdates
y setWebhook
.
Inicio rápido: ejecutar localmente con polling largo
El polling largo es perfecto para el desarrollo local: tu bot pregunta repetidamente a Telegram por nuevas actualizaciones a través de getUpdates
. (En producción, probablemente cambiarás a webhooks.)
Telegram admite dos formas mutuamente excluyentes de recibir actualizaciones:
getUpdates
(polling) o webhooks; las actualizaciones se mantienen durante un máximo de 24 horas.
Opción A: Python (con python-telegram-bot
)
Instalación y scaffolding
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install python-telegram-bot
Código: bot.py
import os
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters
TOKEN = os.environ["TELEGRAM_BOT_TOKEN"] # export TELEGRAM_BOT_TOKEN=123:ABC
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("👋 Hola! Envíame algo y lo repetiré.")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(update.message.text)
def main():
app = ApplicationBuilder().token(TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
app.run_polling() # polling largo
if __name__ == "__main__":
main()
Ejecútalo:
export TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
python bot.py
Esto usa ApplicationBuilder
de los documentos actuales estables.
Opción B: Node.js (con telegraf
)
Instalación y scaffolding
npm init -y
npm i telegraf
Código: index.js
const { Telegraf } = require('telegraf');
const bot = new Telegraf(process.env.BOT_TOKEN); // establece la variable de entorno BOT_TOKEN
bot.start((ctx) => ctx.reply('👋 Hola! Envíame algo y lo repetiré.'));
bot.on('text', (ctx) => ctx.reply(ctx.message.text));
bot.launch();
// parada suave
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));
Ejecútalo:
export BOT_TOKEN=123456:ABC-DEF...
node index.js
Esto se basa en el ejemplo oficial de “Getting started” de Telegraf.
Hazlo útil (comandos y botones)
Añade un comando /help
Python
from telegram.ext import CommandHandler
async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("/start – saludar\n/help – este ayuda")
app.add_handler(CommandHandler("help", help_cmd))
CommandHandler
es la forma estándar de vincular /comandos
.
Node (Telegraf)
bot.help((ctx) => ctx.reply('/start – saludar\n/help – este ayuda'));
Botones de respuesta rápida (teclado personalizado)
Python
from telegram import ReplyKeyboardMarkup
async def start(update: Update, context):
keyboard = [["Ayuda", "Acerca de"]]
await update.message.reply_text(
"Elige una opción:",
reply_markup=ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
)
Node (Telegraf)
const { Markup } = require('telegraf');
bot.start((ctx) => {
ctx.reply("Elige una opción:", Markup.keyboard([["Ayuda", "Acerca de"]]).resize());
});
Producción: cambia a webhooks
En producción, Telegram envía actualizaciones a tu punto final HTTPS. Puedes establecer esto con setWebhook
, opcionalmente proporcionando un token secreto para que puedas verificar la solicitud a través del encabezado X-Telegram-Bot-Api-Secret-Token
.
Establece un webhook con curl
:
TOKEN=123456:ABC-DEF...
WEBHOOK_URL="https://tu-dominio.com/telegram/${TOKEN}" # incluye un camino secreto
SECRET="mi-secreto-42"
curl -X POST "https://api.telegram.org/bot${TOKEN}/setWebhook" \
-d url="${WEBHOOK_URL}" \
-d secret_token="${SECRET}" \
-d drop_pending_updates=true
Puntos clave del especificado:
setWebhook
requiere una URL HTTPS y admitesecret_token
.- Mientras se establece un webhook, no puedes usar
getUpdates
. - Los puertos admitidos incluyen 443, 80, 88, 8443.
Opciones del servidor de webhook
- Node (Telegraf) webhook integrado:
bot.launch({
webhook: {
domain: "tu-dominio.com",
port: 8080,
// token secreto opcional
secretToken: process.env.WEBHOOK_SECRET
}
});
Telegraf expone opciones de webhook de primer nivel e integra con Express/Fastify/Cloudflare Workers, etc.
- Python (
python-telegram-bot
) PTB funciona bien con marcos ASGI/WSGI (FastAPI, Starlette, Flask). UsaApplicationBuilder().token(...).build()
, expone una ruta POST que alimenta las actualizaciones JSON entrantes a tu aplicación y llama abot.set_webhook(...)
. Consulta los documentos deApplicationBuilder
para patrones de construcción. ([docs.python-telegram-bot.org][2])
¿Pruebas localmente? Usa un túnel (por ejemplo,
ngrok
) para exponerhttps://...
a Telegram, luego llama asetWebhook
con la URL pública.
Configuración de comandos del bot (opcional pero amigable para el usuario)
Puedes definir la lista de comandos del bot (lo que ven los usuarios cuando escriben /
) ya sea:
- En BotFather a través de /mybots → Editar Bot → Editar Comandos, o
- Programáticamente con
setMyCommands
usando tu biblioteca o la API de Bot directamente.
La guía oficial de Telegram “Desde BotFather hasta ‘Hello World’” vincula a una gestión más avanzada de comandos y ejemplos si quieres profundizar.
Consideraciones de despliegue
Cualquier host capaz de HTTPS funciona:
- Una pequeña VM (Ubuntu + systemd)
- Sin servidor (AWS Lambda/Cloud Functions) con integración de webhook
- Contenedores en Fly.io/Render/Plataformas similares a Heroku
Los documentos de Telegraf incluyen ejemplos para Lambda, GCF, Express, Fastify, etc.
Lista de verificación de seguridad y fiabilidad
- Mantén el token secreto (variables de entorno, gestor de secretos). Revoca en BotFather si se filtra.
- Usa
secret_token
con webhooks y verifica el encabezadoX-Telegram-Bot-Api-Secret-Token
. - No mezcles polling y webhooks; elige uno a la vez.
- Maneja errores y reintentos: Telegram reintentará respuestas de webhook no 2xx. Registra adecuadamente.
- Sé consciente de los tipos de actualización (mensajes, consultas de devolución, consultas inline, pagos, etc.) y suscríbete solo a lo que necesitas (
allowed_updates
).
Trabajar directamente con la API HTTP (opcional)
Puedes llamar a endpoints como:
https://api.telegram.org/bot<token>/getMe
https://api.telegram.org/bot<token>/getUpdates
https://api.telegram.org/bot<token>/sendMessage
Usa GET o POST con JSON o datos de formulario según la especificación.
Qué hacer y leer a continuación
- Referencia oficial de la API de Bot: métodos, objetos, límites, formato, pagos, modo inline.
- Guía oficial “Desde BotFather hasta ‘Hello World’”: recorrido más profundo y ejemplos en varios idiomas.
- Documentación de python-telegram-bot (estable): patrones modernos asincrónicos.
- Documentación de Telegraf: recetas rápidas, ayudantes de webhook y tipos TS.
Pasos de despliegue en AWS para la versión de Python
Tenemos dos opciones principales para el despliegue del bot de Telegram en la infraestructura de AWS:
- A) Sin servidor (API Gateway + Lambda + Secrets Manager) — más barato y fácil de ejecutar, ideal para tráfico modesto.
- B) Contenedorizado (ECS Fargate + ALB + ACM) — de producción para tráfico estable y bibliotecas de larga ejecución como
python-telegram-bot
en modo webhook.
A) Sin servidor en AWS (API Gateway + Lambda)
Usa esto cuando quieras cero servidores y costo de inactividad cercano a cero. El código a continuación maneja directamente los webhooks de Telegram (sin bucle de eventos de larga ejecución).
- Prepara un manejador mínimo de Lambda (Python)
Crea handler.py
:
import json, os, urllib.request
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
SECRET_HEADER = os.environ.get("WEBHOOK_SECRET", "") # debe coincidir con el secret_token configurado en el webhook
API_BASE = f"https://api.telegram.org/bot{BOT_TOKEN}"
def reply(chat_id: int, text: str):
data = json.dumps({"chat_id": chat_id, "text": text}).encode()
req = urllib.request.Request(f"{API_BASE}/sendMessage", data=data, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
return resp.read()
def lambda_handler(event, context):
# 1) Verifica el encabezado secreto de Telegram (configurado cuando configuras el webhook)
if SECRET_HEADER:
if event.get("headers", {}).get("X-Telegram-Bot-Api-Secret-Token") != SECRET_HEADER:
return {"statusCode": 401, "body": "secreto inválido"}
# 2) Analiza la actualización
body = event.get("body") or "{}"
update = json.loads(body)
message = update.get("message") or update.get("edited_message")
if not message:
# ignora actualizaciones no de mensaje (callback_query, inline_query, etc.) por ahora
return {"statusCode": 200, "body": "ok"}
chat_id = message["chat"]["id"]
text = (message.get("text") or "").strip()
# 3) Enrutamiento simple
if text.startswith("/start"):
reply(chat_id, "👋 Hola desde AWS Lambda! Envíame cualquier texto y lo repetiré.")
elif text.startswith("/help"):
reply(chat_id, "/start – saludar\n/help – ayuda\n(Desplegado en AWS Lambda)")
elif text:
reply(chat_id, text) # repetir
return {"statusCode": 200, "body": "ok"}
Esto usa llamadas HTTPS directas a la API de Bot para mantener Lambda delgado y amigable con los inicios fríos. Puedes expandir el enrutamiento más tarde.
- Empaquetar y desplegar la Lambda
# Diseño del proyecto
# .
# ├─ handler.py
# └─ requirements.txt # (deja vacío para este ejemplo mínimo)
zip -r function.zip handler.py
aws lambda create-function \
--function-name telegram-bot-webhook \
--runtime python3.11 \
--role arn:aws:iam::<ACCOUNT_ID>:role/<LambdaExecutionRole> \
--handler handler.lambda_handler \
--timeout 10 \
--memory-size 256 \
--zip-file fileb://function.zip \
--environment Variables='{TELEGRAM_BOT_TOKEN=<TOKEN_DE_BOTFATHER>,WEBHOOK_SECRET=my-secret-42}'
Rol IAM (<LambdaExecutionRole>
) necesita AWSLambdaBasicExecutionRole
para CloudWatch Logs.
Prefiere almacenar tu token en AWS Secrets Manager y cargarlo en la inicialización—este ejemplo usa variables de entorno por brevedad.
- Crear un punto final HTTPS (API Gateway)
# API HTTP (no REST) para menor latencia
API_ID=$(aws apigatewayv2 create-api \
--name telegram-webhook \
--protocol-type HTTP \
--target arn:aws:lambda:<REGION>:<ACCOUNT_ID>:function:telegram-bot-webhook \
--query 'ApiId' --output text)
# Añadir una ruta predeterminada POST /webhook
aws apigatewayv2 create-integration \
--api-id $API_ID \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:<REGION>:<ACCOUNT_ID>:function:telegram-bot-webhook \
--payload-format-version 2.0
aws apigatewayv2 create-route \
--api-id $API_ID \
--route-key "POST /webhook" \
--target "integrations/$(
aws apigatewayv2 get-integrations --api-id $API_ID --query 'Items[0].IntegrationId' --output text
)"
aws apigatewayv2 create-deployment --api-id $API_ID
GW_URL=$(aws apigatewayv2 get-apis --query "Items[?ApiId=='$API_ID'].ApiEndpoint" --output text)
echo $GW_URL # e.g., https://abc123.execute-api.ap-somewhere.amazonaws.com
Asegúrate de que Lambda permiso se añada automáticamente; si no:
aws lambda add-permission \
--function-name telegram-bot-webhook \
--statement-id apigw \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:<REGION>:<ACCOUNT_ID>:$API_ID/*/*/webhook"
- Dirige a Telegram a tu webhook
TOKEN=<TOKEN_DE_BOTFATHER>
WEBHOOK_URL="$GW_URL/webhook"
SECRET="my-secret-42"
curl -X POST "https://api.telegram.org/bot${TOKEN}/setWebhook" \
-d url="${WEBHOOK_URL}" \
-d secret_token="${SECRET}" \
-d drop_pending_updates=true
Envía /start
a tu bot—los mensajes fluirán a través de API Gateway → Lambda.
- Registros, reintentos y actualizaciones
- Observa los registros:
aws logs tail /aws/lambda/telegram-bot-webhook --follow
- Telegram reintentará cuando tu punto final no sea 2xx; mantén las respuestas rápidas.
- Para desplegar nuevo código: re-zip +
aws lambda update-function-code --function-name telegram-bot-webhook --zip-file fileb://function.zip
.
B) python-telegram-bot
contenedorizado en ECS Fargate + ALB
Usa esto cuando quieras la ergonomía de
python-telegram-bot
(PTB) con una aplicación asincrónica adecuada y conexiones persistentes. Ejecutaremos PTB detrás de un balanceador de carga HTTPS.
- Código de la aplicación (FastAPI + webhook de PTB)
app.py
:
import os, asyncio
from fastapi import FastAPI, Request, Header
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters
TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
SECRET = os.environ.get("WEBHOOK_SECRET", "")
app = FastAPI()
tg_app = None # aplicación PTB
async def start_cmd(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("🚀 Hola desde ECS Fargate + PTB!")
async def echo(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(update.message.text)
@app.on_event("startup")
async def on_startup():
global tg_app
tg_app = ApplicationBuilder().token(TOKEN).build()
tg_app.add_handler(CommandHandler("start", start_cmd))
tg_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# No run_polling(); alimentaremos los webhooks manualmente.
@app.post("/telegram/webhook")
async def telegram_webhook(request: Request, x_telegram_bot_api_secret_token: str | None = Header(default=None)):
if SECRET and (x_telegram_bot_api_secret_token != SECRET):
return {"ok": True} # ignorar silenciosamente
data = await request.json()
update = Update.de_json(data, tg_app.bot)
await tg_app.process_update(update)
return {"ok": True}
Entrada de uvicorn
: uvicorn app:app --host 0.0.0.0 --port 8080
- Dockerfile
FROM python:3.11-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
RUN pip install --no-cache-dir fastapi uvicorn[standard] python-telegram-bot==21.*
COPY app.py .
EXPOSE 8080
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
Construye y empuja:
aws ecr create-repository --repository-name telegram-ptb || true
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=<REGION>
ECR="$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/telegram-ptb"
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com"
docker build -t telegram-ptb .
docker tag telegram-ptb:latest $ECR:latest
docker push $ECR:latest
- Servicio de ECS Fargate detrás de un ALB
- Crea un Grupo de Seguridad que permita el tráfico 443 en el ALB; las tareas de ECS permiten 8080 desde el SG del ALB.
- Crea un Balanceador de Carga de Aplicación (ALB) con un Escuchador HTTPS (443) y un Certificado ACM para tu dominio.
- Grupo de objetivos: HTTP 8080 (tipo de objetivo IP), ruta de verificación
/
(FastAPI sirve 404; puedes añadir@app.get("/")
ruta de salud).
Crea una Definición de Tarea (Fargate) con:
- Imagen del contenedor:
$ECR:latest
- Mapeo de puertos: 8080
- Variables de entorno:
TELEGRAM_BOT_TOKEN
,WEBHOOK_SECRET
- Rol de tarea: rol básico de CloudWatch Logs.
Crea un Servicio de ECS:
- Tipo de lanzamiento Fargate, cuenta deseada 1+.
- Adjunta al grupo de objetivos del ALB.
Nota el dominio HTTPS público del ALB (o usa Route 53 para apuntar tu nombre de DNS).
- Dile a Telegram tu URL de webhook
TOKEN=<TOKEN_DE_BOTFATHER>
SECRET="my-secret-42"
WEBHOOK_URL="https://tu.dominio.com/telegram/webhook"
curl -X POST "https://api.telegram.org/bot${TOKEN}/setWebhook" \
-d url="${WEBHOOK_URL}" \
-d secret_token="${SECRET}" \
-d drop_pending_updates=true \
-d allowed_updates='["message","edited_message","callback_query"]'
- Escalar y operar
- Escala las tareas de ECS arriba y abajo; el ALB distribuirá el tráfico.
- Rota los tokens en Secrets Manager, actualiza el servicio con nuevas variables de entorno de la tarea.
- Usa CloudWatch Logs para registros de la aplicación y registros de acceso del ALB (S3).
¿Cuál debemos elegir?
- Lambda + API Gateway: más simple, más barato en volumen bajo; ideal para bots que hacen unas pocas llamadas por minuto.
- ECS Fargate + ALB: mejor cuando quieres la experiencia completa de
python-telegram-bot
, middlewares personalizados, trabajos en segundo plano y tráfico estable.
Lista rápida de verificación (ambos enfoques)
- Usa punto final HTTPS +
secret_token
y verifica el encabezadoX-Telegram-Bot-Api-Secret-Token
. - Elige webhook O polling (no ambos).
- Persiste la configuración en Secrets Manager, no en el código.
- Añade observabilidad: CloudWatch Logs + métricas (alarmas 5xx).
- Maneja solo los tipos de actualización que necesitas; expande más tarde.
Enlaces útiles
- https://docs.python-telegram-bot.org
- https://telegraf.js.org
- https://core.telegram.org/bots/tutorial
- https://core.telegram.org/bots/api
- Visión general de AWS CDK, ejemplos en TypeScript y Python y consideraciones de rendimiento
- Rendimiento de AWS Lambda: JavaScript vs Python vs Golang
- Lambda con capas en AWS SAM y Python
- AWS SAM + AWS SQS + Python PowerTools
- Hoja de trucos de Python
- uv - Nuevo paquete, proyecto y administrador de entorno de Python
- Instalar Node.js