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 não existe, não está aprovado, ou faltou variável.
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_keynãoChave inválida ou revogada. Crie outra no painel.
rate_limitedsimEspere alguns segundos e tente de novo, com recuo progressivo.
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.
meta_unavailablesimInstabilidade na Meta. Tente de novo daqui a pouco.

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.
Tamanho do texto4096 caracteres.
Variáveis por template20, de 512 caracteres cada.
Idempotency-KeyGuardada por 24 horas.
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_key",
]);

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);
}