Erros da API do WhatsApp: o que cada um significa e como tratar
A mensagem não saiu e o log diz apenas 'erro ao enviar'. Os erros que você vai encontrar de verdade — janela fechada, número inválido, template não aprovado, limite atingido, instância caída — e o tratamento certo para 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
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 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.
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. 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:
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.
Template não aprovado ou pausado
Definitivo, e exige gente:
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:
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.
Instância desconectada
Temporário, mas não adianta repetir em 2 segundos: alguém precisa reconectar.
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 elimina a causa mais comum, que é o celular.
Mídia recusada
Definitivo, quase sempre por URL:
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:
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
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.
Pronto para automatizar seu WhatsApp?
Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.
Começar grátisPerguntas 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.
Continue lendo
Como criar um chatbot de IA com a API da OpenAI para responder no WhatsApp
Um webhook, uma chamada à API da OpenAI e uma resposta pela WAME API: o código completo de um chatbot de IA que atende no WhatsApp, Instagram e Messenger. Com memória por contato, controle de custo e o que fazer quando a IA não deve responder.
Cobrança por Pix dentro do WhatsApp pela API: como enviar e o que muda na conversão
Mandar o código Pix no WhatsApp resolve o pior ponto da cobrança digital: o cliente não precisa sair do app. Como enviar a cobrança pela API, tratar a confirmação e evitar os erros que transformam a facilidade em suporte.
Disparo em massa no WhatsApp: fila, rate limit e retry (a engenharia que o for-loop não resolve)
Um laço for com 5.000 contatos falha na metade e você não sabe em quais. Como montar a fila, respeitar o rate limit da API, aplicar retry com backoff só nos erros que valem e retomar uma campanha interrompida sem enviar nada duas vezes.