Erros e limites

Erro sempre volta com success: false e um objeto error. Trate pelo code, que é contrato e não muda. A message é para humano ler e pode mudar a qualquer momento.

formato
{
  "success": false,
  "error": {
    "code": "outside_24h_window",
    "message": "Faz mais de 24h que essa pessoa não escreve. Envie um template aprovado."
  }
}

Códigos (link para esta seção)

A coluna Repetir é a que importa na hora de escrever o seu retry: repetir o que está marcado com "não" só gasta a sua cota e o limite do seu número na Meta.

codeRepetir?O que fazer
invalid_recipientnãoNúmero fora do formato. Use só dígitos, com país e DDD.
empty_messagenãoFaltou text ou template no corpo.
outside_24h_windownãoPassou da janela de 24h. Reenvie por template aprovado.
invalid_templatenãoTemplate fora da política ou com texto final longo demais.
template_params_mismatchnãoAs variáveis não batem com o template. Cabeçalho com imagem/arquivo, botão com link variável e variáveis com NOME ({{nome}}) também precisam ser preenchidos no envio, não só o corpo.
template_not_foundnãoNome ou idioma do template não existe. Confira em Templates.
template_pausednãoA Meta pausou o template por baixa qualidade. Repetir não resolve.
marketing_experimentnãoA Meta está testando a entrega de marketing e separou este destinatário num grupo que não recebe. Não é falha do envio, e reenviar não muda.
recipient_unreachablenãoO número não tem WhatsApp ou não pode receber.
no_connected_numbernãoNenhum número conectado na conta. Conecte no painel.
invalid_api_keynãoChave inválida ou revogada. Crie outra no painel.
idempotency_in_flightsimOutra chamada com a MESMA Idempotency-Key ainda está sendo processada. Espere alguns segundos e repita com a mesma chave: é a resposta que impede o envio duplo.
api_rate_limitedsimPassou das 600 por minuto da SUA chave. Espere alguns segundos, com recuo progressivo.
rate_limitedsimLimite da META, não o nosso. Espere alguns minutos: insistir aqui piora.
delivery_limitedsimLimite diário do seu número na Meta. Reduza o ritmo e volte depois.
account_restricteddepoisA conta está restrita na Meta. Resolva lá antes de insistir.
payment_issuenãoO pagamento da conta WhatsApp está com problema e a Meta bloqueou o envio. Repetir não resolve: alguém precisa acertar no Gerenciador de Negócios.
meta_unavailablesimInstabilidade na Meta. Tente de novo daqui a pouco.
network_errorsimNão conseguimos falar com a Meta. Falha de rede, vale repetir.
send_failedsimFalha não classificada no envio. Repita uma vez; persistindo, fale com o suporte.
api_rate_limited e rate_limited são coisas diferentes e é fácil confundir. O primeiro é o teto da SUA chave aqui (600 por minuto): passa em segundos. O segundo é a Meta segurando o seu número, e insistir nele piora a situação. Trate os dois com esperas diferentes.

Códigos HTTP (link para esta seção)

HTTPQuando
200Deu certo. Sempre confira success no corpo mesmo assim.
400Corpo inválido (número torto, campo faltando).
401Chave ausente, inválida ou revogada.
404Recurso não existe (ex.: entrega de outro id).
422A Meta recusou o envio (fora da janela, template pendente).
429Passou do limite de requisições.
Não devolvemos 5xx em erro de negócio de propósito. O que fica na frente da API substitui corpo de 5xx por uma página genérica, e aí você perderia justamente o code que explica o que houve.

Limites (link para esta seção)

LimiteValor
Requisições600 por minuto, por CHAVE. Uma chave não atrapalha a outra.
Idempotency-KeyGuardada por pelo menos 24 horas.
Tamanho do texto4096 caracteres.
Variáveis por template20, de 1024 caracteres cada. Acima disso a chamada é recusada, nunca cortada.
Entregas por página200 no máximo.

A Meta tem os limites dela por cima: quantidade de contatos diferentes por dia, que varia com a qualidade do seu número. Isso aparece no painel, em Configurações. Estourar esse limite volta como delivery_limited.

Como escrever o seu retry (link para esta seção)

Recuo progressivo com um pouco de aleatoriedade, e teto de tentativas. Sem a parte aleatória, mil mensagens que falharam juntas voltam juntas e derrubam de novo o que estava se recuperando.

Node.js
const NAO_REPETIR = new Set([
  "invalid_recipient", "empty_message", "outside_24h_window",
  "invalid_template", "recipient_unreachable", "no_connected_number", "invalid_api_key",
  "payment_issue", // conta bloqueada por pagamento: repetir nunca vai passar
]);

async function enviarComRetry(corpo, tentativa = 1) {
  const r = await enviar(corpo);
  if (r.success) return r;
  if (NAO_REPETIR.has(r.error.code) || tentativa >= 5) return r;

  // 2s, 4s, 8s, 16s, mais até 1s de folga aleatória para não voltarem todos juntos.
  const espera = 2 ** tentativa * 1000 + Math.random() * 1000;
  await new Promise((ok) => setTimeout(ok, espera));
  return enviarComRetry(corpo, tentativa + 1);
}