Errores de la API de WhatsApp: qué significa cada uno y cómo manejarlos
El mensaje no salió y el log solo dice 'error al enviar'. Los errores que vas a encontrar de verdad — ventana cerrada, número inválido, plantilla no aprobada, límite alcanzado, instancia caída — y el manejo correcto de cada uno.
"Error al enviar el mensaje." Es lo que registra la mayoría de los logs, y es inútil: no dice si hay que reintentar, avisarle a alguien o desistir.
Este artículo separa los errores que de verdad vas a encontrar y el manejo correcto de cada uno.
Primero: 200 no es entrega
const r = await enviar(to, texto); // 200 OK
// esto NO quiere decir que el mensaje llegóEl 200 significa que la plataforma aceptó el mensaje. La entrega se confirma después, por el webhook de estado — en value.statuses, como delivered o failed.
Quien solo mira la respuesta de la llamada nunca se entera de los fallos de entrega. Por eso medir con el webhook de estado no es opcional.
La división que ordena todo
| Temporal | Definitivo | |
|---|---|---|
| Ejemplos | Límite alcanzado, 5xx, timeout, instancia caída | Número inválido, plantilla no aprobada, ventana cerrada, bloqueado |
| Acción | Reintentar con backoff | No reintentar |
| Registrar como | Pendiente | Fallo, con el motivo |
Reintentar un error definitivo es el desperdicio más común en una cola de envío: gasta cuota, atrasa a quien viene detrás y nunca funciona.
Los errores, uno por uno
Ventana de 24 horas cerrada
El más común de todos entre quienes empiezan con notificaciones.
Intentaste mandar texto libre a alguien que no escribe hace más de 24 horas. En la API oficial, eso no sale.
if (e.code === 'fuera_de_ventana') {
// No reintentes: en 5 minutos dará el mismo error.
// Reenvía como plantilla aprobada.
return enviarPlantilla(to, 'aviso_generico', params);
}El manejo no es un reintento — es usar una plantilla. Si tu flujo manda notificaciones, siempre se va a encontrar con la ventana cerrada, porque el mensaje sale de ti.
Número inválido o sin WhatsApp
Definitivo. Marca y sigue:
if (e.code === 'sin_whatsapp' || e.code === 'numero_invalido') {
await db.contactos.marcar(to, 'invalido');
return; // nunca más intentes con este número
}Prevenir es mejor: verifica la base antes de la campaña. En la API oficial, un intento fallido repetido perjudica la reputación del número. El procedimiento está en limpieza de la lista.
Plantilla no aprobada o pausada
Definitivo, y necesita intervención humana:
if (e.code === 'plantilla_no_aprobada') {
await alertarEquipo(`Plantilla ${nombre} no disponible — campaña pausada`);
await pausarCampana(campanaId);
return;
}Pausa la campaña entera, no solo el mensaje. Si la plantilla se cayó, los próximos 5.000 envíos van a fallar igual — y cada intento empeora el cuadro.
Una plantilla aprobada puede quedar pausada después, si recibe muchos bloqueos. La aprobación no es permanente.
Límite alcanzado
Temporal, y el manejo equivocado lo empeora:
if (e.status === 429) {
const espera = Number(e.headers?.['retry-after'] ?? 60) * 1000;
throw new ErrorTemporal(espera); // la cola se encarga del backoff
}Respeta el Retry-After cuando venga. Sin él, backoff exponencial con jitter — reintentar todo junto en el mismo instante recrea el mismo límite. El diseño completo está en cola, rate limit y reintentos.
Instancia desconectada
Temporal, pero no sirve reintentar en 2 segundos: alguien tiene que reconectar.
if (e.code === 'instancia_desconectada') {
await alertarEquipo(`La instancia de ${cliente.nombre} se cayó`);
await notificarCliente(cliente, 'canal_desconectado');
throw new ErrorTemporal(15 * 60 * 1000); // reintenta en 15 min
}Avisarle al cliente antes de que lo note cambia la conversación: de "tu sistema está roto" a "vimos que se cayó y ya lo estamos resolviendo".
Esto pesa más en la API no oficial, donde la sesión puede caerse sola. La conexión sin celular elimina la causa más común, que es el teléfono.
Archivo rechazado
Definitivo, y casi siempre por la URL:
if (e.code === 'archivo_invalido') {
// Causas: URL no pública, sin HTTPS, archivo demasiado grande,
// formato no soportado, o el servidor devolviendo HTML en vez del archivo.
await db.envios.marcar(id, 'archivo_invalido');
return;
}El caso más traicionero es la URL que exige autenticación: en tu navegador abre porque tienes sesión, y para la plataforma devuelve la página de login. Pruébala siempre en una ventana de incógnito.
El contacto te bloqueó
Definitivo, y es información valiosa:
if (e.code === 'contacto_bloqueo') {
await db.contactos.marcar(to, 'bloqueo');
await registrarEnMetrica(campanaId, 'bloqueo');
return;
}No lo trates como un fallo técnico. El bloqueo es señal de contenido, y es el indicador que avisa antes de que el número se queme.
El handler que lo une todo
const DEFINITIVOS = new Set([
'sin_whatsapp', 'numero_invalido', 'plantilla_no_aprobada',
'contacto_bloqueo', 'archivo_invalido', 'fuera_de_ventana',
]);
async function enviarConManejo(job) {
const { to, payload, campanaId } = job.data;
try {
const r = await api.enviar(to, payload);
await db.envios.exito(campanaId, to, r.messageId);
} catch (e) {
const definitivo = DEFINITIVOS.has(e.code) ||
(e.status >= 400 && e.status < 500 && e.status !== 429);
// Log con lo que permite actuar: código, destinatario, intento.
// "error al enviar" no permite nada.
console.error({
evento: 'fallo_envio',
campana: campanaId,
to,
code: e.code,
status: e.status,
intento: job.attemptsMade + 1,
definitivo,
});
if (definitivo) {
await db.envios.fallo(campanaId, to, e.code);
return; // no reintenta
}
throw e; // reintenta con backoff
}
}Qué registrar
Un log que sirve tiene código, destinatario y número de intento. Con eso respondes las tres preguntas que aparecen cuando algo sale mal:
- ¿Qué error está creciendo hoy?
- ¿Este número falla siempre o fue una vez?
- ¿Estamos reintentando algo que nunca va a funcionar?
Una alerta simple cierra el ciclo: si la tasa de fallo de una campaña pasa del 10%, detente y avisa. Una campaña que falla en masa con la plantilla pausada consume cuota y empeora la reputación en cada intento.
Conclusión
El manejo de errores en una API de mensajería es una sola decisión, repetida: ¿esto cambia si lo intento de nuevo?
Si cambia, es cola y backoff. Si no cambia, es registro con motivo y seguir adelante. Equivocar esa clasificación es lo que hace que una cola se trabe durante horas repitiendo "el número no existe" — o que se rinda con mensajes que habrían salido en el segundo intento.
¿Listo para automatizar tu WhatsApp?
Crea tu cuenta gratis y empieza a enviar mensajes por la API en minutos.
Empezar gratisPreguntas frecuentes
¿Por qué mi mensaje no se entrega aunque reciba un 200?+
Porque el 200 significa que la plataforma aceptó el mensaje para enviarlo, no que llegó. La entrega se confirma después, por el webhook de estado. Si solo miras la respuesta de la llamada, nunca te enteras de que el mensaje falló en la entrega.
¿Qué significa el error de ventana de 24 horas?+
Que intentaste enviar un mensaje libre a alguien que no te escribe hace más de 24 horas. Fuera de esa ventana, la API oficial solo acepta plantillas previamente aprobadas. Es el error más común de quien integra notificaciones por primera vez.
¿Qué hago cuando la API devuelve límite alcanzado?+
Esperar y reintentar con un intervalo creciente y algo de variación aleatoria. Reintentar de inmediato empeora la situación, porque el nuevo intento cae en el mismo límite. El manejo correcto es backoff exponencial con jitter y una cola que controle el ritmo.
¿Cómo sé si la instancia está desconectada antes de enviar?+
Consultando el estado de la instancia. Conviene tener una verificación periódica que alerte antes de que el cliente lo note, y una comprobación en el worker que evite quemar intentos enviando a una instancia que ya se sabe caída.
¿Debo reintentar todos los errores automáticamente?+
No. Reintentar solo tiene sentido en el error temporal: límite, indisponibilidad, timeout y fallo de red. El error definitivo — número inexistente, plantilla no aprobada, contacto bloqueado — no cambia con un nuevo intento, y reintentarlo gasta cuota y atrasa la cola.
Sigue leyendo
API de Instagram y Messenger en la misma API de WhatsApp: una instancia, un estándar
Tres canales oficiales de Meta casi siempre significan tres integraciones. Con una sola instancia, el mismo envío y el mismo sobre de webhook, solo cambia un campo: provider. Cómo funciona y qué te ahorra.
API no oficial de WhatsApp: qué es, si es segura y cómo usarla (2026)
Entiende qué es una API no oficial de WhatsApp, cómo funciona la conexión por código QR, si es segura y legal, cuál es el riesgo real de bloqueo y cuándo conviene usarla en lugar de la API oficial.
API de WhatsApp gratis: cómo probarla y empezar sin costo (2026)
¿Existe una API de WhatsApp gratis? Qué se puede hacer sin pagar, cómo probar la integración sin costo, las limitaciones reales de lo gratuito y cómo hacer el primer envío en minutos.