Quando dá erro
Antes de procurar o código, descubra de onde ele veio. São duas tabelas nesta página, e olhar na errada é meia hora perdida. E existe um terceiro caso, o pior de todos, em que nada dá erro e nada chega: ele tem seção própria aqui embaixo.
Esta lista não é exaustiva, e isso é declarado de propósito.
Só entra aqui o código que a Zera leu no próprio código ou mediu numa chamada real. A coluna onde a Zera viu isto traz a fonte de cada linha. Código que nós conhecemos de ouvir falar fica de fora: uma frase inventada nesta página vira uma hora perdida do seu lado.
De onde veio o erro
São três origens, e cada uma tem um formato diferente. Reconhecer o formato já resolve metade.
| Quem respondeu | Como o erro chega | Onde procurar |
|---|---|---|
| A Graph da Meta | Um objeto error com code numérico, dentro do corpo. Você estava chamando graph.facebook.com direto, com o seu token. |
A tabela Os erros da Meta, aqui embaixo. |
| O painel da Zera | { "ok": false, "erro": "..." }, com uma chave em português. Veio de uma rota /api/..., ou seja, de dentro do painel. |
A tabela As recusas do painel. |
| A janela de conexão | Uma frase em inglês numa janela do Facebook, na hora de conectar o número. Não tem código nem JSON. | O que cada mensagem quer dizer, que é a página dessa etapa. |
Mandei e não chegou
É o caso mais comum de chegar até aqui, e quase sempre ele não é erro nenhum. Percorra nesta ordem, e pare no primeiro que responder não.
- A chamada voltou 200 com um
wamid? Se não voltou, o erro está no corpo da resposta, e o código dele está na tabela da Meta. Vá para ela. - Voltou. Então a Meta aceitou, e o problema não está na sua chamada.
"message_status": "accepted"quer dizer aceita, nunca entregue. - A janela de 24 horas está aberta? Texto livre só chega para quem escreveu para o seu número nas últimas 24 horas. Fora dela, o caminho é modelo aprovado. Detalhe em A janela de 24 horas e em Modelos de mensagem.
- O destino está em E.164 sem o sinal de mais? Um celular de São Paulo fica
5511987654321. Sem espaço, sem parêntese e sem traço. - Você está esperando a resposta da pessoa, e não a sua ida? Então leia O erro que não dá erro antes de continuar depurando. É o caso em que o seu código está certo e a entrega nunca foi apontada para você.
O formato do erro da Meta
A Meta devolve o erro dentro do corpo, e não só no status. Estes são os campos que o produto da Zera lê de verdade quando trata uma falha da Graph.
{
"error": {
"message": "...",
"type": "OAuthException",
"code": 190,
"error_subcode": ...,
"error_data": { "details": "..." },
"fbtrace_id": "..."
}
}
- Decida pelo
code, nunca pelomessage. É regra escrita no nosso executor, e ela veio da própria documentação da Meta, que avisa para não construir lógica em cima do texto. Umifque procura palavra dentro da frase quebra no dia em que a Meta reescrever a frase, e quebra em silêncio. - A mesma frase junta causas diferentes. A Graph responde does not exist, cannot be loaded due to missing permissions, or does not support this operation para três situações distintas. Quem lê a frase acerta o caso inofensivo e engole o perigoso.
error_data.detailsé onde mora o tempo de espera. Quando a Meta manda esperar, ela diz quanto ali dentro, em texto livre. Não crave um número no seu código: leia o que veio.error_subcodeaparece só em parte dos erros, e ele afina ocode. O valor está com reticências acima de propósito: cravar um número ali seria um exemplo que ninguém mediu. Um subcódigo que a Zera mediu é o33, que vem junto docode100 quando o identificador do caminho não existe.fbtrace_idé o que o suporte da Meta pede. Guarde no seu registro, junto com o horário.
Os erros da Meta
Estes são os códigos que a Zera trata no próprio código ou mediu em chamada real. Cada linha diz onde a Zera viu.
| Código | O que quer dizer | O que fazer | Onde a Zera viu isto |
|---|---|---|---|
190 |
A autorização não vale mais. Token errado, vencido ou revogado dentro da Meta. | Reconecte o número no painel. A Zera não gera outra credencial e não revoga a atual: só a Meta faz as duas coisas. | _saude-meta.js, conjunto de revogação. _es-executor.js, passo que lê a conta. Medido: token ruim numa rota real devolve 190. |
200 |
O token existe e não alcança o que você pediu. É falta de permissão, não de validade. | Reconecte o número. Se ele aparecer só no envio, o suspeito é o escopo de mensagens: leia O escopo que ninguém confere. | _saude-meta.js, mesmo conjunto do 190. _chatwoot.js: no modelo de provedora de tecnologia a conta é do cliente, e um token da Zera toma erro 200 nela. |
100 |
Campo ou parâmetro que não existe nessa versão. Um único campo desconhecido no fields derruba a resposta inteira, e não só aquele campo. |
Peça poucos campos por chamada e vá tirando um a um. Parâmetro a mais também cai aqui, sem dizer qual sobrou. | _saude-meta.js: é o motivo de a leitura de saúde ser três chamadas separadas. _es-executor.js, passo do OAuth. Medido em v25.0: GET /{waba_id}/phone_numbers sem token devolve 100, com Tried accessing nonexisting field (phone_numbers). Vale para borda de subrecurso, e não para toda chamada sem token: veja a linha do 104. |
104 |
A chamada foi sem token, num nó que pede token para ser lido. A mensagem é An access token is required to request this resource. | Mande o cabeçalho Authorization: Bearer. Se você acha que mandou, confira se o cliente HTTP não está descartando o cabeçalho num redirecionamento. |
Medido em v25.0: GET /{id}?fields=messaging_limit_tier sem token devolve 104. Este código não está em nenhuma lista do nosso executor, então ele cai no balde dos definitivos. |
2500 |
Duas causas, e a mensagem separa as duas. Unknown path components quer dizer que sobrou segmento no caminho. An active access token must be used quer dizer que a chamada foi sem token para um nó que depende do usuário corrente, como /me. |
Leia a mensagem antes de mexer no caminho. Se ela falar de access token, o problema é a autorização, e conferir o caminho é meia hora perdida. Se falar de path components, confira a versão e o caminho, nesta ordem. | Medido em v25.0: /{waba_id}/phone_numbers/extra devolve 2500 com Unknown path components: /extra, e GET /me sem token devolve 2500 com a mensagem de token. Um identificador inventado, ao contrário, devolve 100 com error_subcode 33, e não 2500. |
4, 613, 80007 |
Limite de chamadas. O 4 merece atenção: ele é o teto do aplicativo inteiro, e não da sua conta. |
Espere e reduza a frequência. Não repita em laço: insistir num limite consome o orçamento sem destravar nada. | _saude-meta.js, conjunto de limite de chamadas. _es-executor.js traz a nota sobre o 4 ser do aplicativo inteiro. |
2 |
Indisponibilidade passageira do lado da Meta. | Tente de novo daqui a pouco. Uma vez, não em laço. | _es-executor.js, conjunto de retentáveis, com a citação da própria Meta. |
368 |
Conta restrita por violação de política. | Resolva no Gerenciador da Meta. Repetir a chamada não destrava, e a documentação da Meta se contradiz sobre este código: no contexto de conta de WhatsApp vale a leitura de restrição. | _es-executor.js, conjunto de definitivos, com o comentário sobre a contradição. admin/desligar.js. |
3, 10 |
A Zera não traduz cada um. O produto os põe no mesmo balde do 190, do 200 e do 368: credencial morta. | Nenhuma tentativa nossa resolve. Quem resolve é o dono da conta, dentro da Meta. | admin/desligar.js: a lista [190, 200, 368, 10, 3], com a frase só o cliente consegue remover a inscrição. |
133008, 133009, 133016 |
A Meta mandou esperar, e diz quanto dentro de error_data.details. |
Leia o tempo que veio e espere exatamente ele. Chutar um número esperando de menos queima tentativa do orçamento da Meta. | _es-executor.js, conjunto de espera com prazo, com o [HARD] proibindo cravar o tempo no código. |
Os outros que o produto classifica sem traduzir
O executor da Zera separa mais alguns códigos em duas caixas, para decidir se tenta de novo. Ele não traduz cada um, e esta página também não vai inventar a frase de nenhum deles. O que dá para dizer com honestidade é a caixa em que cada um caiu.
- Tratados como passageiros:
1,17,341,80008,131000,133004e133015. Tentar de novo daqui a pouco tem chance. - Tratados como definitivos:
131031,133005,133006e133010. Insistir não destrava e ainda gasta chamada. - Código que não está em nenhuma das listas também é tratado como definitivo, e isso é escolha, não descuido. Insistir num erro que ninguém entende, num endereço que cria conta de verdade na Meta, é como se descobre um limite depois de queimar todas as tentativas dele.
Se você bater num código que não está nesta página, a autoridade é a referência de erros da Meta. E escreva para a Zera: a sua medição vira uma linha nova aqui.
As recusas do painel
Estas são as respostas das rotas /api/... do painel. Elas não são uma API pública da Zera: não existe chave da Zera, não existe endereço da Zera no caminho do seu envio, e o que você faz com a credencial vai direto para a Meta. Elas aparecem quando o painel recusa alguma coisa na sua frente, e a chave é sempre a mesma palavra.
{ "ok": false, "erro": "sessao_velha" }
Algumas respostas trazem um campo a mais, e ele é útil: teto_por_dia diz qual é o limite que você encostou, proxima_em diz a partir de quando dá para tentar de novo, e lido_em diz de quando é o valor que continua na tela.
proxima_em e lido_em vêm em segundos desde 1970, e não em milissegundos. Em JavaScript, comparar com Date.now() direto dá uma espera de dezenas de milhares de anos, ou de zero, e não levanta erro nenhum. Compare com Math.floor(Date.now() / 1000).
| Chave | HTTP | O que quer dizer | O que fazer |
|---|---|---|---|
sessao |
401 | A sua sessão não foi reconhecida. | Peça um link de acesso novo pelo e-mail do cadastro. |
sessao_velha |
403 | Duas ações exigem um acesso recém-feito, com menos de dez minutos de vida: exibir a credencial e salvar o endereço de webhook. Estar logado não basta. O salvamento entrou nessa lista em 23 de setembro de 2026, quando a escrita na Meta foi ligada em produção: antes disso ele só gravava uma coluna, e agora ele redireciona o recebimento do número. A trava existe no código desde 15 de setembro, e só vale com a escrita ligada. | Peça um link novo por e-mail, ou entre de novo com a senha. As duas ações ficam liberadas pelos dez minutos seguintes. |
origem |
403 | O pedido de escrita se identificou como vindo de outra origem que não a do painel. | Use o painel. A guarda olha os cabeçalhos Sec-Fetch-Site e Origin, e ela falha fechada num caso específico: quando o pedido diz de onde veio e nós não conseguimos determinar qual é a nossa própria origem, ela recusa. Quem segura um pedido que não se identifica é o cookie de sessão, que esta rota exige do mesmo jeito. |
so_o_dono |
403 | Quem entrou por convite recebe esta recusa, mesmo com acesso ao painel e mesmo com uma sessão recém-feita, em dois lugares que pegam quem integra: ao exibir a credencial e ao salvar o endereço do webhook. No salvamento, ela vem antes das travas próprias dele (endereço, plano, teto, sessão recém-feita e aviso ao dono): nada é gravado, nenhum e-mail sai e a Meta não é chamada. O bloco do webhook aparece para o convidado do mesmo jeito, então a recusa só chega quando ele clica em salvar. | Peça ao dono da conta que exiba a credencial e salve o endereço. É a trava que mais pega desenvolvedor, porque o normal é o cliente convidar quem vai integrar. Os detalhes estão em A sua credencial e em Receber mensagens. |
plano_com_inbox |
409 | O seu plano inclui a caixa de entrada da Zera, e o recebimento deste número já é dela. Hoje isto é recusa do salvamento do endereço de webhook, e não da exibição da credencial: desde 06 de setembro de 2026 a credencial é exibida em todo plano. | Nada. Quem atende pela plataforma da Zera já recebe as mensagens por ela, e um segundo endereço seria um campo que ninguém lê. A credencial continua saindo normalmente, e ela serve para o seu sistema enviar. |
webhook_meta |
502 | Só existe ao apagar o endereço, salvando em branco: a Meta recusou apagar, ou o e-mail de aviso ao dono da conta não foi aceito pelo provedor e a Meta não chegou a ser tocada, de propósito. Nada foi apagado. O corpo traz um objeto meta com estado, erro, em e pnid. Ao apontar, este código não aparece, nem quando a Meta recusa: a resposta é HTTP 200 com "ok": true, e quem diz o que aconteceu é o campo meta. meta.estado igual a "erro" é tentativa que não fechou. A ausência do campo meta é o sinal de que nenhuma tentativa na Meta aconteceu: o e-mail de aviso não foi aceito, o número não concluiu a conexão, ou a escrita está desligada do nosso lado. |
Olhe o campo meta, e não o status. Em meta.erro: falha_nossa é do nosso lado, e nem todo caminho que leva a ele gera alerta, então escreva para a Zera se ele continuar depois de alguns minutos; endereco_longo quer dizer endereço acima de 200 caracteres, recusado por nós antes de a Meta ser chamada; retentavel:0 quer dizer que a Meta não respondeu, ou respondeu com uma falha do lado dela, e não que ela recusou; divergente: quer dizer que a Meta está entregando em outro lugar; ambiente: e orcamento:, sempre com código negativo, são nossos; qualquer outro classe:código quer dizer que a Meta não confirmou o endereço, e o erro pode ter vindo da releitura, depois de uma escrita que valeu, ou ser limite de chamadas (espera): trate como não confirmado, e não como recusado. Se o código for definitivo:190 ou definitivo:200, a autorização do número deixou de valer na Meta: salvar de novo não resolve e manda outro e-mail ao dono a cada tentativa. Reconecte o número e só então salve o endereço de novo. Sem meta, trate o 200 como não apontado. Tente de novo, e se insistir escreva para a Zera. O detalhe está em O que a resposta do salvamento diz. |
sem_waba |
409 | Este número ainda não passou pela conexão com a Meta, então ainda não existe credencial para ele. | Conecte o número. A credencial é criada pela Meta nesse momento. |
sem_token |
409 | O número foi conectado antes de a Zera passar a guardar a autorização. | Reconecte o número. |
sem_numero |
409 | A conta existe e o número ainda não foi registrado na Meta, então não há o que ler. | Termine a conexão. |
token_vencido |
409 | A credencial guardada já venceu. Ela não é exibida de propósito. | Reconecte. Entregar uma chave morta custaria meia hora sua depurando um problema que é da conexão. |
autorizacao_revogada |
409 | A Meta respondeu 190 ou 200 na leitura. |
Reconecte o número. É a mesma história das duas primeiras linhas da tabela da Meta. |
conta_cancelada |
409 | A conta está cancelada. Ela continua lendo o histórico e não escreve mais nada. | Para voltar, refaça o cadastro com este mesmo e-mail. |
webhook_cliente |
400 | O endereço de webhook foi recusado. Ele precisa ser uma URL completa, em https, com host, e com no máximo 200 caracteres, que é o teto da Meta para override_callback_uri. |
Corrija o endereço. Acima de 200 caracteres ele é recusado e não cortado, de propósito: um endereço cortado passaria calado e a Meta passaria a entregar num destino diferente do que está no seu servidor. |
teto_do_dia |
429 | São três exibições de credencial por conta, e conta a tentativa; a que falha por problema nosso é devolvida. A janela é de 24 horas corridas, contadas a partir de cada exibição, e não da meia-noite. | Espere a janela virar: o limite libera 24 horas depois da primeira das três exibições, e não à meia-noite. Se você precisa dela agora, escreva para a Zera. |
teto_da_hora |
429 | Você encostou num teto por hora, por conta. O número muda com a rota: são vinte nas leituras que vão à Meta, trinta no documento de fatura, e dez nas duas escritas na Meta que usam este código: salvar o perfil do número e criar um modelo novo, cada uma com a sua contagem. Os vinte das leituras são contados em separado para cada uma das quatro (perfil, modelos, métricas e estado do número), e não somados num balde só: gastar as vinte de Modelos não tira nenhuma de Perfil. A troca do endereço de webhook também tem teto de dez por hora, mas responde com outro código, rate. Este teto é da Zera, e o da Meta continua existindo por baixo dele. |
Espere. Numa leitura, o que está na tela continua valendo, com a data de leitura ao lado. Numa escrita, nada foi enviado para a Meta: tente de novo depois que a hora virar. |
muito_cedo |
429 | Duas leituras seguidas rápido demais. O estado do número tem freio de cinco minutos, e ele vale para toda leitura. Nas outras abas o freio é de um minuto e só é avaliado quando você pede atualização forçada: sem forçar, a resposta pode vir de um cache de até dez minutos, e nesse caso ela chega sem recusa nenhuma. | Olhe o proxima_em da resposta e espere até lá. Para saber de quando é o que você está lendo, olhe o lido_em. |
rate |
429 | Você encostou no teto por hora daquela rota. O número muda de rota para rota, então esta chave sozinha não diz qual limite você bateu: na troca de endereço de webhook são dez tentativas de salvar por hora, por conta, contando as recusadas pela trava da sessão, e nas rotas de cadastro, senha, entrada e pagamento os números são outros. | Espere uma hora e repare no que você estava fazendo quando a recusa veio. Bater neste teto costuma ser laço de script, e não uso de gente. |
meta_limitou |
429 | A Meta respondeu 4, 613 ou 80007 na leitura. |
Espere. Assim que ela liberar, a leitura volta sozinha. |
nao_encontrado |
404 | O número não está nesta conta, ou o identificador está fora de formato. As duas respostas são idênticas de propósito: um 403 confirmaria que o identificador existe. | Volte para a lista e abra um dos seus números. |
campo_indisponivel |
502 | A Meta respondeu 100 para aquele campo, na versão que usamos hoje. |
Nada do seu lado. O campo aparece como não lido em vez de aparecer com um valor plausível. |
aviso_nao_saiu |
503 | Passou em tudo e o e-mail de aviso ao dono da conta não saiu, então a credencial não saiu junto. | Tente de novo em alguns minutos. O aviso não é formalidade: como a Zera não consegue revogar a credencial, ele é a única forma de alguém descobrir uma exibição indevida. |
falha_nossa |
503 | O cofre não abriu, a rede caiu, ou algo do nosso lado não respondeu. | Tente de novo em alguns minutos. Se continuar, escreva para a Zera: nem todo caminho que leva a este código gera alerta do nosso lado. |
indisponivel |
503 | A operação não foi concluída, e nada foi gravado pela metade. | Tente de novo em alguns minutos. |
corpo, json |
400 | O corpo do pedido veio vazio, grande demais, ou não era JSON válido. | Confira o que o seu cliente HTTP está enviando. |
interno |
500 | Falha nossa. Nenhuma resposta que chega ao painel carrega detalhe, e isso é de propósito: a mensagem crua de um erro de banco carrega nome de coluna e às vezes o próprio valor. | Escreva para a Zera com o horário. É um dos poucos casos desta tabela em que você não tem o que fazer sozinho. |
O erro que não dá erro
Três casos em que nada falha, nada aparece no seu registro, e a mensagem nunca chega.
Nenhum dos três produz um código de erro. É por isso que eles têm seção própria: quem procura o defeito na tabela de códigos passa o dia procurando uma linha que não existe.
1. O webhook apontado no nível da conta, que some na reconexão
Esta seção mudou em 23 de setembro de 2026. Até 22 de setembro ela dizia que o campo de webhook do painel guardava o endereço e não configurava nada na Meta. Isso era verdade e deixou de ser: desde 23 de setembro, com a escrita ligada em produção, o salvamento aponta a sobreposição no seu número. O código que faz isso existe desde 15 de setembro, e até o dia 22 ficou desligado. O caso que sobra é outro, e continua sendo um erro que não dá erro.
A Meta aceita sobreposição de endereço em três níveis: número, conta de WhatsApp e aplicativo. Apontar no nível da conta funciona, a releitura confirma, as mensagens começam a chegar, e um dia elas param.
Por quê: toda vez que o seu número é reconectado, por autorização revogada ou por troca de número, a Zera assina o aplicativo dela na sua conta com POST /{waba_id}/subscribed_apps sem corpo. A própria Meta documenta que assinar sem parâmetros apaga a sobreposição da conta. A sobreposição do número não é tocada por isso, e tem precedência sobre a da conta.
O sintoma: tudo funcionou por semanas, o número foi reconectado num dia qualquer, e a partir dali o seu servidor parou de receber. Nada dá erro, nenhuma configuração sua mudou, e o painel não tem como saber de uma sobreposição que ele não escreveu.
O que fazer: aponte pelo painel, em Canais, no número, aba Detalhes, cartão Ajustes de API. Ele escreve no nível do número e relê para confirmar. O passo a passo, e o caminho manual para quando o painel devolver erro, estão em Receber mensagens. Aquela página também diz, com todas as letras, que nenhum número percorreu isso de ponta a ponta em produção até hoje.
2. O escopo que ninguém confere
A conexão do número confere que o seu token carrega whatsapp_business_management, que é o escopo de gerenciar a conta. Se ele não carregar esse escopo com conta nenhuma, a conexão falha ali mesmo, com mensagem clara.
O escopo que faz POST /{phone_number_id}/messages funcionar é outro: whatsapp_business_messaging. Ele não é conferido em lugar nenhum do produto. A busca por esse nome nas pastas de código, functions/ e public/js/, devolve zero ocorrências: ele só aparece em páginas desta documentação. E é para você poder conferir por conta própria, não para você acreditar na nossa palavra.
Quais escopos a janela de conexão pede é configuração do Embedded Signup, guardada na Meta atrás do identificador de configuração, e ela vive fora deste repositório. Então esta página não afirma que o seu token tem o escopo de envio, nem que ele é pedido: ela afirma a única coisa que dá para conferir aqui, que é que nada verifica.
O sintoma: a conexão termina bem, o painel mostra o número, a leitura de estado na Meta funciona, e o primeiro envio volta erro 200. Como as leituras usam o escopo de gerenciamento, tudo o que você olha na tela parece saudável.
O que fazer: reconecte o número e autorize tudo o que a Meta pedir na janela. Se o 200 voltar no envio depois disso, escreva para a Zera antes de reescrever o seu código: o problema não está nele.
3. Aceita não é entregue
A resposta de um envio bem-sucedido traz "message_status": "accepted" e um wamid. Aceita quer dizer que a Meta recebeu a sua mensagem e assumiu a entrega. Não quer dizer que ela chegou no aparelho de alguém.
Entrega e leitura existem em um lugar só: no corpo do webhook, dentro de statuses[], com o mesmo wamid que voltou no envio. Ou seja, enquanto o seu servidor não estiver recebendo os eventos daquele número, você não tem como saber se alguma mensagem sua chegou.
O que fazer agora: guarde o wamid de todo envio, mesmo antes de o recebimento estar de pé. Sem ele, quando os eventos começarem a chegar, entrega e leitura chegam sem dono e não dá para ligar nenhuma delas ao que você mandou.
Quando nada disso resolve
Escreva para [email protected]. Um chamado com estas cinco coisas costuma ser respondido na primeira resposta; sem elas, a primeira resposta é um pedido de informação.
- A resposta crua da Graph, o objeto
errorinteiro, comcode,error_subcode,error_data.details,fbtrace_idemessage. Copie como veio, sem resumir: ofbtrace_idé o que o suporte da Meta pede. - O
wamid, quando houve envio. É por ele que nós encontramos a mensagem do lado da Meta. - O horário, com o fuso. Sem fuso, três horas de diferença viram três horas de procura no lugar errado.
- O método e o caminho exatos que você chamou, incluindo a versão. Sem o token, e sem o cabeçalho
Authorization. - O telefone e o
phone_number_iddo número envolvido.
Nunca mande o token.
Nós não precisamos dele para investigar, e ele não pode ser trocado depois: a Zera não consegue gerar outro nem revogar o atual. Se você já colou o token em algum lugar, o caminho é a Meta, no Gerenciador de Negócios da sua empresa.