---
title: "Errores de la API de WhatsApp: qué significa cada uno y cómo manejarlos"
description: "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."
url: "https://wame.api.br/blog/errores-api-whatsapp-como-manejarlos"
language: "pt-BR"
og:type: "article"
published_time: "2026-09-10"
modified_time: "2026-09-10"
author: "Raphael Serafim"
keywords: "error api whatsapp, mensaje no enviado whatsapp api, rate limit whatsapp, whatsapp api error, manejo de errores api whatsapp, instancia desconectada whatsapp"
reading_time: "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.

**"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

```js
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](/blog/metricas-campana-whatsapp-api) 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.

```js
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](/blog/plantillas-whatsapp-api-crear-aprobar-enviar). 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:

```js
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](/blog/lista-contactos-optin-optout-whatsapp).

### Plantilla no aprobada o pausada

Definitivo, y necesita intervención humana:

```js
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:

```js
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](/blog/cola-rate-limit-reintentos-envio-masivo-whatsapp).

### Instancia desconectada

Temporal, pero no sirve reintentar en 2 segundos: alguien tiene que reconectar.

```js
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](/blog/whatsapp-sin-celular-conexion-movil-api) elimina la causa más común, que es el teléfono.

### Archivo rechazado

Definitivo, y casi siempre por la URL:

```js
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:

```js
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

```js
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.

## Perguntas frequentes

### ¿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.
