---
title: "Erros da API do WhatsApp: o que cada um significa e como tratar"
description: "A mensagem não saiu e o log só diz 'erro ao enviar'. Os erros reais — janela fechada, número inválido, template recusado — e como tratar cada um."
url: "https://wame.api.br/blog/erros-api-whatsapp-como-tratar"
language: "pt-BR"
og:type: "article"
published_time: "2026-09-10"
modified_time: "2026-09-10"
author: "Raphael Serafim"
keywords: "erro api whatsapp, mensagem não enviada whatsapp api, erro 131047 whatsapp, rate limit whatsapp, whatsapp api error, tratamento de erro api whatsapp, instância desconectada whatsapp"
reading_time: "9 min de leitura"
---

# Erros da API do WhatsApp: o que cada um significa e como tratar

> A mensagem não saiu e o log só diz 'erro ao enviar'. Os erros reais — janela fechada, número inválido, template recusado — e como tratar cada um.

**"Erro ao enviar mensagem."** É o que a maioria dos logs registra, e é inútil: não diz se é para repetir, avisar alguém ou desistir.

Este artigo separa os erros que você de fato vai encontrar e o tratamento certo para cada um.

## Primeiro: 200 não é entrega

```js
const r = await enviar(to, texto);   // 200 OK
// isso NÃO quer dizer que a mensagem chegou
```

`200` significa que a plataforma **aceitou** a mensagem. A entrega é confirmada depois, pelo webhook de status — em `value.statuses`, como `delivered` ou `failed`.

Quem só olha o retorno da chamada nunca fica sabendo das falhas de entrega. É por isso que [medir com o webhook de status](/blog/metricas-campanha-whatsapp-api) não é opcional.

## A divisória que organiza tudo

| | Temporário | Definitivo |
|---|---|---|
| **Exemplos** | Limite atingido, 5xx, timeout, instância caída | Número inválido, template não aprovado, janela fechada, bloqueado |
| **Ação** | Repetir com backoff | Não repetir |
| **Registrar como** | Pendente | Falha, com o motivo |

Repetir erro definitivo é o desperdício mais comum em fila de disparo: gasta cota, atrasa quem está atrás e nunca dá certo.

## Os erros, um a um

### Janela de 24 horas fechada

**O mais comum de todos** em quem está começando com notificação.

Você tentou mandar texto livre para alguém que não escreve há mais de 24 horas. Na API oficial, isso não sai.

```js
if (e.code === 'fora_da_janela') {
  // Não repita: em 5 minutos vai dar o mesmo erro.
  // Reenvie como template aprovado.
  return enviarTemplate(to, 'aviso_generico', params);
}
```

O tratamento não é retry — é [usar template](/blog/templates-whatsapp-api-criar-aprovar-enviar). Se o seu fluxo manda notificação, ele **sempre** vai encontrar a janela fechada, porque a mensagem parte de você.

### Número inválido ou sem WhatsApp

Definitivo. Marque e siga:

```js
if (e.code === 'sem_whatsapp' || e.code === 'numero_invalido') {
  await db.contatos.marcar(to, 'invalido');
  return;   // nunca mais tente este número
}
```

Prevenir é melhor: verifique a base **antes** da campanha. Na API oficial, tentativa falha repetida prejudica a reputação do número. O procedimento está em [higienização de lista](/blog/lista-contatos-optin-optout-whatsapp).

### Template não aprovado ou pausado

Definitivo, e exige gente:

```js
if (e.code === 'template_nao_aprovado') {
  await alertarTime(`Template ${nome} indisponível — campanha pausada`);
  await pausarCampanha(campanhaId);
  return;
}
```

**Pause a campanha inteira**, não só a mensagem. Se o template caiu, as próximas 5.000 vão falhar igual — e cada tentativa piora o quadro.

Template aprovado pode ser **pausado** depois, se receber muito bloqueio. Aprovação não é permanente.

### Limite atingido

Temporário, e o tratamento errado piora:

```js
if (e.status === 429) {
  const espera = Number(e.headers?.['retry-after'] ?? 60) * 1000;
  throw new ErroTemporario(espera);   // a fila cuida do backoff
}
```

Respeite o `Retry-After` quando ele vier. Sem ele, backoff exponencial com jitter — repetir tudo junto no mesmo instante recria o mesmo limite. O desenho completo está em [fila, rate limit e retry](/blog/fila-rate-limit-retry-disparo-whatsapp).

### Instância desconectada

Temporário, mas não adianta repetir em 2 segundos: alguém precisa reconectar.

```js
if (e.code === 'instancia_desconectada') {
  await alertarTime(`Instância de ${cliente.nome} caiu`);
  await notificarCliente(cliente, 'canal_desconectado');
  throw new ErroTemporario(15 * 60 * 1000);   // tenta de novo em 15 min
}
```

Avisar o cliente **antes** de ele perceber muda a conversa: de "seu sistema está quebrado" para "vimos que caiu e já estamos resolvendo".

Isso pesa mais na API não oficial, em que a sessão pode cair sozinha. A [Conexão Mobile](/blog/whatsapp-sem-celular-conexao-mobile-api) elimina a causa mais comum, que é o celular.

### Mídia recusada

Definitivo, quase sempre por URL:

```js
if (e.code === 'midia_invalida') {
  // Causas: URL não pública, sem HTTPS, arquivo grande demais,
  // formato não suportado, ou o servidor devolvendo HTML no lugar do arquivo.
  await db.envios.marcar(id, 'midia_invalida');
  return;
}
```

O caso mais traiçoeiro é a URL que exige autenticação: no seu navegador abre (você tem sessão), e para a plataforma volta a página de login. Teste sempre em janela anônima.

### Contato bloqueou

Definitivo, e é informação valiosa:

```js
if (e.code === 'contato_bloqueou') {
  await db.contatos.marcar(to, 'bloqueou');
  await registrarNaMetrica(campanhaId, 'bloqueio');
  return;
}
```

Não trate como falha técnica. Bloqueio é **sinal de conteúdo**, e é o indicador que avisa antes de o número queimar.

## O handler que amarra tudo

```js
const DEFINITIVOS = new Set([
  'sem_whatsapp', 'numero_invalido', 'template_nao_aprovado',
  'contato_bloqueou', 'midia_invalida', 'fora_da_janela',
]);

async function enviarComTratamento(job) {
  const { to, payload, campanhaId } = job.data;

  try {
    const r = await api.enviar(to, payload);
    await db.envios.sucesso(campanhaId, to, r.messageId);
  } catch (e) {
    const definitivo = DEFINITIVOS.has(e.code) ||
                       (e.status >= 400 && e.status < 500 && e.status !== 429);

    // Log com o que permite agir: código, destinatário, tentativa.
    // "erro ao enviar" não permite nada.
    console.error({
      evento: 'falha_envio',
      campanha: campanhaId,
      to,
      code: e.code,
      status: e.status,
      tentativa: job.attemptsMade + 1,
      definitivo,
    });

    if (definitivo) {
      await db.envios.falha(campanhaId, to, e.code);
      return;   // não repete
    }
    throw e;    // repete com backoff
  }
}
```

## O que registrar

Log que serve tem **código, destinatário e número da tentativa**. Com isso você responde as três perguntas que aparecem quando algo dá errado:

- Qual erro está crescendo hoje?
- Este número falha sempre ou foi uma vez?
- Estamos repetindo algo que nunca vai funcionar?

Um alerta simples fecha o ciclo: **se a taxa de falha de uma campanha passar de 10%, pare e avise**. Campanha que falha em massa com o template pausado consome cota e piora a reputação a cada tentativa.

## Conclusão

Tratamento de erro em API de mensagem é uma decisão só, repetida: **isto muda se eu tentar de novo?**

Se muda, é fila e backoff. Se não muda, é registro com motivo e seguir adiante. Errar essa classificação é o que faz uma fila travar por horas repetindo "número não existe" — ou desistir de mensagens que teriam saído na segunda tentativa.

## Perguntas frequentes

### Por que minha mensagem não é entregue mesmo com status 200?

Porque 200 significa que a plataforma aceitou a mensagem para envio, não que ela chegou. A entrega é confirmada depois, pelo webhook de status. Se você só olha o retorno da chamada, nunca vai saber que a mensagem falhou na entrega.

### O que significa o erro de janela de 24 horas?

Que você tentou enviar mensagem livre para alguém que não escreve há mais de 24 horas. Fora dessa janela, a API oficial só aceita template previamente aprovado. É o erro mais comum em quem está integrando notificações pela primeira vez.

### O que fazer quando a API retorna limite atingido?

Esperar e repetir com intervalo crescente e alguma variação aleatória. Repetir imediatamente piora a situação, porque a nova tentativa cai no mesmo limite. O tratamento correto é backoff exponencial com jitter, e uma fila que controle o ritmo para o limite não ser atingido de novo.

### Como saber se a instância está desconectada antes de tentar enviar?

Consultando o estado da instância. Vale ter uma verificação periódica que alerta antes de o cliente perceber, e uma verificação no worker que evita queimar tentativas enviando para uma instância que já se sabe fora do ar.

### Devo repetir todo erro automaticamente?

Não. Repetir só faz sentido em erro temporário: limite, indisponibilidade, timeout e falha de rede. Erro definitivo — número inexistente, template não aprovado, contato bloqueado — não muda com nova tentativa; repetir gasta cota e atrasa a fila.
