Raphael Serafim· Publicado el 10 de septiembre de 2026· 9 min de lectura

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.

Ver como Markdown

"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

javascript
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

TemporalDefinitivo
EjemplosLímite alcanzado, 5xx, timeout, instancia caídaNúmero inválido, plantilla no aprobada, ventana cerrada, bloqueado
AcciónReintentar con backoffNo reintentar
Registrar comoPendienteFallo, 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.

javascript
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:

javascript
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:

javascript
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:

javascript
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.

javascript
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:

javascript
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:

javascript
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

javascript
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 gratis

Preguntas 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