Antes de empezar
Si todavía decides entre los dos modos, lee primero webhooks vs polling. Para un webhook, la Bot API exige:
- Una URL pública HTTPS. Telegram solo admite los puertos 443, 80, 88 y 8443.
- Un certificado válido. Uno autofirmado funciona si lo subes con el parámetro
certificate; la mayoría de hostings (Railway, Cloudflare Workers, un VPS con Caddy) ya te dan un certificado de confianza. - Un handler que responda rápido con HTTP 2xx. Telegram considera falla cualquier otro estado y reintenta.
Mientras haya un webhook configurado, getUpdates no funciona. Usas un modo o el otro.
1. Elige la URL y un secreto
Usa una ruta difícil de adivinar y genera un valor secreto aparte:
export TOKEN="123456:ABC..." # de @BotFather, fuera de git
export HOOK_URL="https://bot.example.com/telegram/webhook"
export HOOK_SECRET="$(openssl rand -hex 32)"
El secret_token puede tener 1-256 caracteres con A-Z, a-z, 0-9, _ y -. Una cadena hexadecimal de 64 caracteres cabe.
2. Llama a setWebhook
curl -s "https://api.telegram.org/bot$TOKEN/setWebhook" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"$HOOK_URL\",
\"secret_token\": \"$HOOK_SECRET\",
\"allowed_updates\": [\"message\", \"callback_query\"],
\"drop_pending_updates\": true,
\"max_connections\": 40
}"
Qué hace cada parámetro:
| Parámetro | Para qué sirve |
|---|---|
url |
Dónde Telegram hace POST de los updates. Una cadena vacía quita el webhook. |
secret_token |
Se devuelve en el encabezado X-Telegram-Bot-Api-Secret-Token de cada petición. |
allowed_updates |
Solo se entregan estos tipos de update. Si lo omites, se conserva el ajuste anterior. |
drop_pending_updates |
Descarta la cola acumulada mientras el bot estaba caído. |
max_connections |
Conexiones HTTPS simultáneas, 1-100 (40 por defecto). Bájalo en un servidor pequeño. |
ip_address |
IP fija a usar en lugar de resolver DNS. |
El constructor de URLs de Telegram arma estas llamadas, y el probador de webhooks sirve para probarlas.
3. Verifica el secreto en tu handler
Quien conozca la URL puede enviar updates falsos. Compara el encabezado con tu secreto y rechaza lo que no coincida antes de hacer cualquier trabajo:
// Cloudflare Worker o cualquier runtime tipo fetch
export default {
async fetch(req, env) {
if (req.method !== "POST") return new Response("ok");
if (req.headers.get("X-Telegram-Bot-Api-Secret-Token") !== env.HOOK_SECRET) {
return new Response("forbidden", { status: 401 });
}
const update = await req.json();
// pasa el trabajo lento a una cola o waitUntil y responde rápido
return new Response("ok");
},
};
Los frameworks lo hacen por ti si les pasas el secreto: webhookCallback(bot, "cloudflare-mod", { secretToken }) en grammY, run_webhook(secret_token=...) en python-telegram-bot y SimpleRequestHandler(secret_token=...) en aiogram.
4. Revisa getWebhookInfo
curl -s "https://api.telegram.org/bot$TOKEN/getWebhookInfo"
Los campos que importan al depurar:
url: vacío significa que no hay webhook (o que alguien llamó adeleteWebhook).pending_update_count: si crece, las entregas están fallando.last_error_dateylast_error_message: la razón exacta de la última falla.allowed_updates: confirma los tipos que esperas.
Valores comunes de last_error_message y cómo resolverlos:
| Mensaje (abreviado) | Causa probable |
|---|---|
Wrong response from the webhook: 401 / 403 |
Tu verificación del secreto rechaza a Telegram; no coincide el nombre o el valor del encabezado. |
Wrong response from the webhook: 404 |
La ruta no coincide con la que sirve tu app. |
Wrong response from the webhook: 500 / 502 |
Tu handler falló o la app está caída. Revisa los logs. |
SSL error ... / certificate verify failed |
Cadena de certificados incompleta o certificado autofirmado sin subir. |
Connection timed out / Read timeout expired |
Handler demasiado lento o un firewall bloquea a Telegram. |
Para ver exactamente lo que envía Telegram, apunta un bot de prueba a una URL temporal de nuestro inspector de webhooks y revisa encabezados y cuerpo.
5. Responde rápido, trabaja después
Telegram sigue reintentando las entregas fallidas, así que un handler lento puede recibir el mismo update otra vez. Devuelve 200 rápido y pasa el trabajo lento a una cola o tarea en segundo plano. Elimina duplicados por update_id si el procesamiento tiene efectos.
Un atajo útil: puedes responder directamente en el cuerpo de la respuesta del webhook devolviendo JSON con un campo method, por ejemplo {"method":"sendMessage","chat_id":123,"text":"Hola"}. No recibes el resultado de esa llamada, así que úsalo solo para respuestas simples.
Volver a polling
curl -s "https://api.telegram.org/bot$TOKEN/deleteWebhook?drop_pending_updates=true"
Sin deleteWebhook, un bot local que use getUpdates recibe un error 409 Conflict.
Preguntas frecuentes
¿Por qué mi bot recibe el mismo mensaje dos veces?
Tu endpoint devolvió un estado distinto de 2xx o tardó demasiado, y Telegram reintentó. Responde de inmediato y procesa en segundo plano.
¿Puedo correr dos instancias del bot con un webhook?
Sí, detrás de un balanceador, porque cada update es una petición HTTP aparte. Es una ventaja frente al long polling, donde solo puede haber un consumidor de getUpdates.
¿Cuánto guarda Telegram los updates si mi servidor está caído?
Los updates sin procesar se guardan del lado de Telegram hasta entregarse, pero no más de 24 horas, según la documentación de la Bot API.
Fuentes
- Telegram Bot API, setWebhook y getWebhookInfo: core.telegram.org/bots/api#setwebhook
- Telegram, guía de webhooks: core.telegram.org/bots/webhooks
- Telegram Bot API, peticiones al recibir updates: core.telegram.org/bots/api#making-requests-when-getting-updates
- grammY, tipos de despliegue: grammy.dev/guide/deployment-types