Cómo enviar mensajes con la API de Telegram: sendMessage paso a paso
La API de Telegram para enviar mensajes es el método sendMessage de la Bot API: haces una petición a https://api.telegram.org/bot<token>/sendMessage con chat_id y text, y Telegram entrega el mensaje como tu bot. Aquí tienes ejemplos listos para copiar en curl, Python y JavaScript, además de formato, botones y los errores más comunes.
Formato de la petición sendMessage
Puedes usar GET o POST y enviar los parámetros como query string, application/x-www-form-urlencoded, application/json o multipart/form-data. Solo dos son obligatorios:
chat_id: id numérico del chat o@usernamede un canal o supergrupo público. Si no lo tienes, consulta cómo obtener el chat id.text: de 1 a 4096 caracteres después de procesar el formato.
La respuesta siempre trae ok. Si es true, en result recibes el Message enviado, con su message_id; si es false, llegan error_code y description.
Enviar un mensaje con curl, Python y JavaScript
curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/sendMessage" \
-H "Content-Type: application/json" \
-d '{"chat_id": 123456789, "text": "Hola desde la Bot API"}'
import requests
TOKEN = "123456789:AAEexampleTokenReplaceWithYours"
resp = requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
json={"chat_id": 123456789, "text": "Hola desde Python"},
timeout=10,
)
data = resp.json()
print(data["result"]["message_id"] if data["ok"] else data["description"])
// Node.js 18+ (módulo ES): fetch viene incluido
const TOKEN = "123456789:AAEexampleTokenReplaceWithYours";
const res = await fetch(`https://api.telegram.org/bot${TOKEN}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: 123456789, text: "Hola desde JavaScript" }),
});
const data = await res.json();
console.log(data.ok ? data.result.message_id : data.description);
Ejecuta este código en tu servidor, nunca en una página web pública: cualquiera podría leer el token en el JavaScript del navegador.
Formato del texto: parse_mode HTML y MarkdownV2
Con "parse_mode": "HTML" puedes usar <b>, <i>, <u>, <s>, <tg-spoiler>, <a href="…">, <code>, <pre> y <blockquote>, además de sus variantes <strong>, <em>, <ins>, <del> y <blockquote expandable>. Todo <, > o & que no forme parte de una etiqueta debe escribirse como <, > y &; en Python, html.escape() lo hace por ti.
{
"chat_id": 123456789,
"parse_mode": "HTML",
"text": "<b>Pedido #154</b> confirmado\nTotal: <code>1250 MXN</code>\n<a href=\"https://example.com/pedidos/154\">Ver pedido</a>"
}
Con "parse_mode": "MarkdownV2" la sintaxis es más corta (*negrita*, _cursiva_, [enlace](https://example.com)), pero fuera de las entidades hay que escapar con \ estos caracteres: _ * [ ] ( ) ~ ` > # + - = | { } . !. Olvidarlo es la causa más común del error 400 Bad Request: can't parse entities. Una función de escape en Python:
import re
def escape_md2(text: str) -> str:
return re.sub(r"([_*\[\]()~`>#+\-=|{}.!\\])", r"\\\1", text)
text = "*Total:* " + escape_md2("1,250.00 MXN (pagado!)")
Ojo: si escribes el texto a mano dentro de un JSON, la barra invertida también se escapa, así que \. se convierte en "\\.".
Botones: teclado inline con reply_markup
{
"chat_id": 123456789,
"text": "¿Confirmas tu pedido?",
"reply_markup": {
"inline_keyboard": [[
{"text": "Ver en el sitio", "url": "https://example.com/pedidos/154"},
{"text": "Confirmar", "callback_data": "confirmar:154"}
]]
}
}
Cada fila es un arreglo de botones. Un botón url abre un enlace; uno con callback_data (de 1 a 64 bytes: los acentos y emojis ocupan más de un byte) envía a tu bot una actualización callback_query. Respóndela siempre con answerCallbackQuery, porque hasta entonces el cliente muestra un indicador de progreso. Si envías los parámetros como formulario en lugar de JSON, reply_markup debe ir serializado como cadena JSON.
Enviar a grupos, canales y temas
- Grupos: usa el id negativo del grupo (en supergrupos empieza por
-100). El bot debe ser miembro. - Canales: el bot debe ser administrador con permiso para publicar. En canales públicos sirve
"chat_id": "@nombredelcanal"; en privados, el id-100…. - Temas de foro: añade
message_thread_idpara publicar en un tema concreto.
Mensajes silenciosos, vista previa de enlaces y respuestas
{
"chat_id": 123456789,
"text": "Reporte diario: https://example.com/reporte",
"disable_notification": true,
"protect_content": true,
"link_preview_options": {"is_disabled": true},
"reply_parameters": {"message_id": 42}
}
disable_notification: el usuario recibe la notificación sin sonido.link_preview_options:is_disabledquita la vista previa; también puedes indicar otraurl,prefer_large_mediaoshow_above_text.protect_content: impide reenviar y guardar el mensaje.reply_parameters: responde a un mensaje concreto.
Errores comunes al enviar mensajes
| Error | Causa y solución |
|---|---|
400 Bad Request: chat not found | chat_id incorrecto, el bot no está en el grupo o canal, o el usuario nunca inició el bot. |
400 Bad Request: can't parse entities | Etiqueta HTML mal cerrada o carácter sin escapar en MarkdownV2. |
400 Bad Request: message is too long | Más de 4096 caracteres: divide el texto. |
403 Forbidden: bot was blocked by the user | El usuario bloqueó el bot. No reintentes; márcalo como inactivo. |
429 Too Many Requests: retry after 35 | Superaste los límites. Espera los segundos de parameters.retry_after. |
Como referencia: no más de 1 mensaje por segundo en un mismo chat, 20 por minuto en un grupo y unos 30 por segundo en envíos masivos. Un reintento sencillo en Python:
import time
import requests
def send(token, chat_id, text):
url = f"https://api.telegram.org/bot{token}/sendMessage"
while True:
data = requests.post(url, json={"chat_id": chat_id, "text": text}, timeout=10).json()
if data.get("error_code") != 429:
return data
time.sleep(data["parameters"]["retry_after"])
Antes de escribir código, prueba tus parámetros en sendMessage en Bot API Tester (Ctrl+Enter ejecuta la petición) y consulta la referencia del método, en inglés. Si además tu bot debe recibir mensajes en un servidor, sigue con la guía de webhook para bots de Telegram.
Preguntas frecuentes
¿Cuál es el límite de caracteres de sendMessage?
El texto debe tener de 1 a 4096 caracteres después de procesar el formato. Si es más largo, divídelo en varios mensajes.
¿Puedo enviar un mensaje a alguien que nunca habló con mi bot?
No. Los bots no pueden iniciar conversaciones: el usuario debe abrir el bot y pulsar Iniciar (o añadirlo a un grupo) antes de que puedas escribirle.
¿Cómo envío un mensaje sin sonido?
Agrega disable_notification: true a la petición: el mensaje llega con una notificación sin sonido.
¿Qué parse_mode conviene usar, HTML o MarkdownV2?
HTML suele ser más sencillo, porque solo hay que escapar los signos menor que, mayor que y el ampersand. MarkdownV2 exige escapar 18 caracteres especiales con una barra invertida. El modo Markdown clásico se mantiene solo por compatibilidad.
¿Cómo envío un mensaje a un canal de Telegram con la API?
Haz al bot administrador del canal con permiso para publicar y usa como chat_id el @username del canal, si es público, o su id numérico, que empieza por -100.
¿Cómo edito o borro un mensaje que ya envié?
Guarda el message_id que devuelve sendMessage y úsalo con editMessageText o deleteMessage.
Guías relacionadas
Cómo crear un bot en Telegram paso a paso: de BotFather a tu primer mensaje
Aprende a crear un bot de Telegram: /newbot en @BotFather, token, prueba con getMe y tu primer mensaje con curl y Python. Gratis y sin servidor.
Cómo obtener el token de un bot de Telegram (y qué hacer si se filtra)
Cómo obtener el token de tu bot de Telegram con @BotFather, encontrarlo si el bot ya existe, comprobarlo con getMe y revocarlo si se filtró.
Webhook de Telegram: cómo configurar setWebhook para tu bot (Python y n8n)
Configura el webhook de tu bot de Telegram: requisitos HTTPS, setWebhook con secret_token, getWebhookInfo, handler en Python y errores típicos en n8n.
Cómo obtener el chat id de Telegram: usuarios, grupos, canales y temas
Obtén el chat id de Telegram para tu bot con getUpdates: usuarios, grupos, supergrupos (-100…), canales y temas, y qué hacer si result viene vacío.