Como o que chega cai no seu servidor
Desde 23 de setembro de 2026 o painel aponta o seu endereço na Meta, e você não precisa mais fazer essa chamada. O código que faz isso existe desde 15 de setembro, mas ficou desligado até o dia 22: nesse intervalo, salvar o endereço só guardava. Esta página diz o que o painel faz, o que o seu servidor tem de responder, e o único limite que você não contorna sozinho.
Leia isto antes de escrever a primeira linha do seu servidor: você não consegue validar a assinatura dos eventos.
Todo POST da Meta chega com o cabeçalho X-Hub-Signature-256, que é um HMAC SHA-256 do corpo cru. Ele é calculado com o segredo do aplicativo que está inscrito na sua conta, e esse aplicativo é o da Zera. O segredo é um só para todos os clientes, não vai na credencial, e não vai passar a ir: segredo de aplicativo compartilhado deixa de ser segredo no primeiro vazamento.
O que você faz no lugar, e vale a pena fazer: manter o endereço do webhook sem nada adivinhável no caminho, aceitar só https, guardar o código de verificação que o painel devolve (ele é gerado por nós e é único do seu número), e tratar todo corpo recebido como não confiável até casar o wamid com algum envio ou conversa que você conhece. A seção Como conferir que veio mesmo da Meta detalha isso.
Se a validação de assinatura for exigência do seu lado, escreva para [email protected] antes de começar. É um caso que a Zera quer conduzir junto, e não um caso para você contornar sozinho.
Quem aponta o quê
A Meta aceita endereço de entrega em três níveis, e na hora de entregar olha nesta ordem: primeiro o número, depois a conta de WhatsApp, depois o aplicativo.
- Aplicativo: é o padrão. Sem nenhuma sobreposição, a Meta entrega no endereço do aplicativo da Zera. A conexão do seu número faz
POST /{waba_id}/subscribed_appssem corpo, que inscreve o aplicativo na sua conta e, como diz o item Conta abaixo, apaga a sobreposição desse nível. - Número: é onde o painel escreve o seu endereço, e é o nível que passa a valer para o seu número. É o que esta página descreve.
- Conta: não use. A própria Meta documenta que assinar o aplicativo sem parâmetros apaga a sobreposição da conta, e a Zera refaz essa assinatura a cada reconexão do seu número. Um endereço apontado no nível da conta funcionaria até a próxima reconexão e sumiria ali, em silêncio, sem erro nenhum. Se você já apontou no nível da conta, refaça no nível do número.
Nenhum número passou por este caminho em produção até hoje.
A escrita foi construída, tem prova em código e foi ligada em produção em 23 de setembro de 2026. O que não existe ainda é um número real que tenha percorrido isto de ponta a ponta. Se você for o primeiro, escreva para a Zera ao terminar, dando certo ou dando errado: a sua tentativa vira a nossa medição, e é a diferença entre esta página descrever o que o produto faz e descrever o que o produto já entregou.
O caminho pelo painel
O lugar é Canais, o seu número, aba Detalhes, cartão Ajustes de API, bloco O endereço para onde os eventos deste número devem ir. Você informa o endereço, salva, e o painel faz o resto.
A conexão do número vem primeiro.
Enquanto o número não concluiu a conexão com a Meta, salvar o endereço só guarda: nenhum e-mail sai, a Meta não é chamada, a resposta vem sem o campo meta, e a linha de estado diz Guardado aqui, ainda não apontado na Meta. O bloco avisa isso acima do campo. Mesmo sem a conexão, o salvamento pede a sessão de dez minutos da tabela abaixo. Conclua a conexão e salve o endereço de novo: é esse segundo salvamento que aponta pelo painel.
Depois que a conexão fecha, um endereço guardado antes dela, pela ficha do número ou por um salvamento, também pode ser apontado pela rotina de operação da Zera, sem e-mail ao dono e sem mostrar o código na tela. Nesse caso, o código está no campo webhook_verify_token da exibição da credencial. Por isso, deixe o seu servidor no ar antes de concluir a conexão.
Com a conexão concluída, o salvamento executa esta sequência, nesta ordem:
- Confere o endereço:
httpsobrigatório, com host, no máximo 200 caracteres. Acima disso ele é recusado, e não cortado: um endereço cortado passaria na validação e a Meta receberia um destino diferente do que está no seu servidor. - Manda um e-mail ao dono da conta avisando da troca, antes de tocar na Meta. Se o provedor de e-mail não aceitar a mensagem, a Meta não é tocada naquele salvamento. O endereço fica gravado, no passo seguinte, e a rotina de operação da Zera pode apontá-lo depois, sem mandar outro e-mail. Veja O que a resposta do salvamento diz.
- Grava o endereço no nosso banco.
- Escreve na Meta:
POST /{phone_number_id}comwebhook_configuration, levando o seu endereço emoverride_callback_urie umverify_tokengerado por nós. - Relê, com
GET /{phone_number_id}?fields=webhook_configuration, e grava o que a Meta devolveu. Resposta de escrita não é prova: o que decide o estado que aparece na sua tela é a releitura.
O código de verificação é gerado por nós, e aparece uma vez
Você não inventa mais o verify_token. Ele é gerado na primeira tentativa de apontar o seu número, com 32 bytes aleatórios, e guardado cifrado do nosso lado antes de ir para a Meta. Quando essa tentativa é um salvamento seu e termina com o endereço apontado, ele é devolvido na tela uma única vez: na mesma resposta do salvamento, no campo webhook_verify_token, num bloco com botão de copiar. Cole ele no seu servidor naquele momento.
Se a Meta recusar a primeira tentativa, o código existe e não aparece. Ele já foi gerado e guardado antes de a Meta ser chamada, mas a resposta de um apontamento que não fechou não o traz. O mesmo vale quando a primeira tentativa foi da rotina de operação da Zera: o código foi gerado ali e nunca apareceu na tela. Numa falha nossa que acontece antes disso, ele pode nem ter sido gerado, e aí a credencial devolve null e a tela diz que não veio código. Nesses casos, e também se você perder o valor, ele volta no campo webhook_verify_token da exibição da credencial, com as travas daquela rota: a tela o mostra logo abaixo do token, com rótulo e botão de copiar próprios. Não há outra porta, e isso é de propósito.
Trocar o endereço do mesmo número depois não gera um código novo: o mesmo valor é reusado. Um número novo, mesmo apontando para o mesmo endereço, ganha o código dele, e o seu servidor precisa aceitar os dois. Ele pode já estar colado no seu servidor, e gerar outro em silêncio faria a verificação da Meta falhar para sempre sem ninguém entender por quê.
As travas do salvamento, e o motivo de cada uma
| Trava | Código | Por que ela existe |
|---|---|---|
| Só o dono da conta salva | so_o_dono, 403 |
Quem entrou por convite vê o bloco do webhook e o endereço guardado, mas o salvamento é recusado antes de qualquer outra trava desta tabela: nada é gravado, nenhum e-mail sai e a Meta não é chamada. É o mesmo código que o convidado recebe ao tentar exibir a credencial. Se quem vai integrar é um desenvolvedor convidado, o endereço é salvo pelo dono da conta. |
| Sessão com menos de dez minutos de vida | sessao_velha, 403 |
Este salvamento redireciona o recebimento do seu número. O acesso ao painel vale 30 dias; um cookie roubado de um notebook esquecido passaria a mandar cada mensagem da sua empresa para outro servidor. É a mesma trava da exibição da credencial, e pelo mesmo motivo. Ela vale também enquanto o número não concluiu a conexão, quando o salvamento só guarda. Entre de novo, pelo link do e-mail ou pela senha, e a janela reabre. |
| Aviso ao dono da conta | Ao apontar: nenhum código, 200 sem o campo meta. Ao apagar: webhook_meta, 502 |
O e-mail sai antes da Meta ser tocada, e se ele não for aceito a Meta não é tocada naquele salvamento. Ao apontar, isso não vira erro: o endereço fica gravado do nosso lado e a resposta é HTTP 200, com "ok": true e sem o campo meta. A ausência desse campo é o sinal de que nenhuma tentativa na Meta aconteceu, e é o que separa este caso de um apontamento de verdade, que sempre traz meta. O endereço gravado assim pode ser apontado depois pela rotina de operação da Zera, sem outro e-mail. Ao apagar, nada é apagado e a resposta é webhook_meta, 502. O dono da conta é quem tem de saber que o destino das mensagens mudou. |
| Dez tentativas de salvar por hora, por conta, contando as recusadas pela trava da sessão | rate, 429 |
Trocar de endereço é raro de verdade. O teto existe para o dia em que um laço de script bater aqui, e a chamada que fala com a rede fica atrás dele. Ele é conferido antes da trava da sessão, então uma tentativa recusada com sessao_velha também conta. |
| Endereço válido | webhook_cliente, 400 |
https, com host, no máximo 200 caracteres, que é o teto da Meta para override_callback_uri. |
| Plano sem caixa de entrada | plano_com_inbox, 409 |
Quem atende pela plataforma da Zera já tem o destino apontado para a nossa caixa. Um segundo endereço aqui seria um campo que ninguém lê, com cara de estar configurado. Não é esquecimento, e não muda com o ZeraSync incluindo a API do WhatsApp: a credencial serve para o seu sistema enviar, e o recebimento continua sendo da caixa. Se o endereço da caixa sobrevive a uma reconexão do número, a Zera ainda não mediu: veja O que a Zera não faz. |
O que a tela diz depois de salvar
O bloco do webhook passa a mostrar uma linha de estado, e ela vem da releitura, não do salvamento:
- Apontado na Meta em data e hora. A releitura confirmou o seu endereço naquela data e hora. É o desfecho bom de um salvamento, e não uma garantia que se renova sozinha: a linha é o que ficou gravado do último apontamento que deu certo, e a tela não relê a Meta quando você a abre. Ela pode continuar dizendo isto depois que deixou de valer, em dois casos: o número foi reconectado e ganhou outro
phone_number_id, e a sobreposição ficou no identificador antigo; ou alguém sobrepôs outro endereço fora do painel. Nesses casos, salve o endereço de novo, ou releia você mesmo com o caminho manual. A tela só mostra esta frase quando o endereço guardado é o mesmo que a Meta confirmou: se você salvou um endereço novo e ele não foi apontado, por exemplo porque o e-mail de aviso ao dono não foi aceito, a linha passa a dizer Guardado aqui, ainda não apontado na Meta. - A Zera está aplicando este endereço na Meta agora. A escrita começou e ainda não fechou. Atualize a página em instantes. Esta frase só aparece quando o último apontamento confirmado tem menos de cinco minutos. Fora disso, uma escrita ainda em andamento aparece como falha nossa: atualize a página em um minuto antes de concluir.
- Guardado aqui, ainda não apontado na Meta. O endereço está no nosso banco e não houve apontamento dele, ou o último endereço que a Meta confirmou é outro. Se o número ainda não concluiu a conexão, conclua primeiro: salvar antes disso só guarda. Com a conexão concluída e logo depois de um salvamento seu, isso quer dizer, no caso comum, que o aviso por e-mail ao dono não saiu, e a tela diz também Endereço guardado, mas não apontado na Meta. Tente de novo em alguns minutos e, se repetir, escreva para a Zera: uma conta sem e-mail no cadastro, por exemplo, repete o mesmo resultado a cada salvamento.
- A Meta está entregando em outro endereço. A releitura trouxe um endereço diferente do seu. Pode ser uma sobreposição feita a mão fora do painel, ou dois salvamentos quase ao mesmo tempo, em que o segundo passou por cima do primeiro enquanto ele falava com a Meta. Salve de novo pelo painel. Pode ser também a Meta devolvendo o mesmo endereço escrito de outro jeito, porque a comparação é caractere por caractere; a Zera ainda não mediu se ela normaliza. Escreva o endereço com o host em minúsculas.
- A Meta não confirmou este endereço, com o código ao lado. O código é a classe e o número do erro, como
definitivo:100. 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. Salve de novo em alguns minutos. Se o código fordefinitivo:190oudefinitivo: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. Ao apagar, a tela diz que a Meta não confirmou a remoção: trate o endereço como ainda apontado. - A Meta não respondeu, ou respondeu com uma falha do lado dela. É o código
retentavel:0: rede que caiu, tempo esgotado ou erro 5xx sem código de erro da Meta. Nada foi recusado. Salve de novo em alguns minutos. - O endereço passa dos 200 caracteres que a Meta aceita. É o código
endereco_longo, e a recusa é nossa: a Zera não chegou a mandar o endereço para a Meta. - A falha é nossa. Alguma coisa do nosso lado não respondeu: o endereço está guardado e o apontamento não saiu. Nem todo caminho que leva a esta frase gera alerta do nosso lado, então, se ela continuar depois de alguns minutos, escreva para [email protected].
Se a resposta do salvamento não chegar à tela, por rede caída ou tempo esgotado, a tela diz A resposta não chegou até esta tela, e não que nada foi gravado. Com a escrita ligada, o salvamento espera o e-mail de aviso e duas chamadas à Meta, e o endereço pode ter sido gravado, apontado e ganhado um código de verificação numa resposta que se perdeu. Recarregue a página e confira a linha de estado antes de salvar de novo; o código, se não apareceu, está na exibição da credencial.
Quando a falha é ao apagar, a tela diz isso na hora, acima dos botões: O endereço não foi apagado, e a Meta continua entregando nele, se a Meta nem chegou a ser chamada e o endereço era o último que ela confirmou; O endereço não foi apagado, e continua guardado aqui, nos outros casos, seguida do motivo. Nada é apagado do nosso lado enquanto a Meta não confirmar a remoção.
O que a resposta do salvamento diz
Se o seu sistema lê a resposta desse salvamento, olhe o campo meta, e não o status HTTP. Ao apontar, toda resposta que passou pelas travas da tabela acima é HTTP 200 com "ok": true, inclusive quando a Meta recusou o endereço, porque o endereço já foi gravado do nosso lado antes de a Meta ser chamada. A exceção é indisponivel, 503, ou interno, 500: o banco não gravou o endereço, e nesse caso o e-mail de aviso pode já ter saído sem nada ter mudado.
meta.estadoigual a"apontado": a releitura na Meta confirmou o seu endereço. Se este mesmo salvamento gerou o código de verificação, a resposta trazwebhook_verify_token. Se o código já existia, ele não vem, e está na exibição da credencial.meta.estadoigual a"erro": houve tentativa e ela não fechou. O motivo está emmeta.erro:falha_nossa,endereco_longo,divergente:seguido da origem do endereço que a Meta devolveu, ou a classe e o código do erro, comodefinitivo:100. Essa classe com código quer dizer que a Meta não confirmou, e não que ela recusou: o erro pode ter vindo da releitura, depois de uma escrita que valeu.retentavel:0quer dizer que a Meta não respondeu, ou respondeu com uma falha do lado dela, eambiente:eorcamento:, sempre com código negativo, são falhas nossas. O endereço continua gravado.meta.estadoigual anull: só ao apagar, e quer dizer que a Meta confirmou que não há mais sobreposição no número.- Sem o campo
meta: nenhuma tentativa na Meta aconteceu. As causas são três: o e-mail de aviso ao dono da conta não foi aceito, o número ainda não concluiu a conexão, ou a escrita na Meta está desligada do nosso lado. A resposta sozinha não diz qual das três foi. Nada apareceu como erro e a Meta não foi tocada: não trate este 200 como apontado.
Ao apagar é diferente, e aí o 502 existe: se a Meta recusar, ou se o e-mail de aviso não sair, a resposta é webhook_meta, HTTP 502, com o objeto meta no corpo, e nada é apagado.
Apagar o endereço
Salvar com o campo em branco apaga a sobreposição. Aqui a ordem se inverte de propósito: a Meta vem primeiro, e o nosso banco depois. Se a Meta recusar, o endereço fica guardado. Apagar do nosso lado antes deixaria a Meta entregando num endereço que ninguém mais teria como nomear. Se apagar responder interno, 500, ou indisponivel, 503, a Meta pode já ter confirmado a remoção com o endereço ainda guardado aqui: recarregue e apague de novo, senão a rotina de operação pode apontá-lo outra vez.
O caminho manual, com o seu próprio token
Este é o contrato da Meta, e é o mesmo que o painel executa. Ele está aqui por dois motivos: para você conferir o que o painel fez, e para o dia em que o painel devolver erro e você precisar destravar sozinho.
Saiba o que acontece quando você volta ao painel depois. Salvar o endereço no painel, com o número conectado, aponta de novo, e com o código de verificação que o painel guardou: se ele ainda não guardou nenhum, gera um agora. Ou seja, informar no painel o mesmo endereço que você apontou a mão troca o código que está na Meta pelo do painel. Faça isso quando quiser voltar para o caminho do painel, e troque o código no seu servidor pelo que o painel devolver, na mesma hora. Enquanto você ficar no caminho manual, o painel não sabe do seu apontamento, e o suporte vai olhar o painel primeiro: avise a Zera.
Aponte no NÚMERO, nunca na conta, pelo motivo já explicado em Quem aponta o quê: a assinatura sem corpo que a Zera refaz a cada reconexão apaga a sobreposição da conta.
1. Sobrepor o endereço de entrega do seu número
Os dois primeiros valores saem do painel, em Canais, no seu número, na aba Detalhes. O verify_token é o código de verificação que o painel devolveu quando você salvou o endereço, ou o campo webhook_verify_token da exibição da credencial. Se a exibição da credencial mostra webhook_verify_token igual a null, ele ainda não existe, e aí vale qualquer frase longa e secreta que você escolher e guardar.
Se você inventou o seu próprio código aqui, o painel não o conhece.
O painel reusa o código que ele guardou, e só gera um novo quando não há nenhum. Um código que você escolheu a mão nunca chegou ao nosso cofre. Então, no dia em que você salvar o endereço pelo painel, ele vai apontar com o código que guardou, ou gerar um se não houver nenhum, e sobrepor o seu na Meta. A rotina de operação da Zera pode fazer o mesmo sem você salvar nada, se o painel tiver guardado um endereço que ainda não confirmou na Meta, como o da ficha do número. Nos dois casos, o seu servidor passa a receber uma verificação com um valor que ele não reconhece. Escolha um caminho e fique nele, ou troque o valor no seu servidor pelo que o painel devolver.
curl -X POST "https://graph.facebook.com/v25.0/SEU_PHONE_NUMBER_ID" \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook_configuration": {
"override_callback_uri": "https://seusistema.com.br/whatsapp",
"verify_token": "UMA_FRASE_LONGA_E_SECRETA_SUA"
}
}'
As barras invertidas no fim das linhas são a continuação de linha do terminal do Linux e do macOS, e o comando roda igual no Git Bash e no WSL. No Windows PowerShell 5.1, que é o que vem instalado no Windows, ele não roda: lá curl é apelido de outro comando, e o corpo perde as aspas no caminho até o programa. No PowerShell 7 ele roda. O jeito de escrever que serve no 5.1 está medido em O primeiro dia.
{ "success": true }
2. Reler, porque OK de escrita não é prova
Releia a sobreposição depois de gravar. É a mesma regra que o nosso próprio código segue, e é por isso que o estado que aparece no painel vem desta leitura, e não da resposta do POST: uma escrita que responde OK sem ter mudado nada é o pior defeito possível, porque parece que funciona.
Releia no NÚMERO, e olhe o campo phone_number de dentro de webhook_configuration. O mesmo bloco traz também whatsapp_business_account e application, que são as sobreposições da conta e do aplicativo. Ler um desses faria um número sem sobreposição nenhuma parecer apontado, herdando o endereço de outro nível.
curl "https://graph.facebook.com/v25.0/SEU_PHONE_NUMBER_ID?fields=webhook_configuration" \
-H "Authorization: Bearer SEU_TOKEN"
Este também não roda como está no Windows PowerShell 5.1: lá curl é apelido de Invoke-WebRequest, e a resposta é uma reclamação sobre o parâmetro Headers. Use o Git Bash, o WSL ou o PowerShell 7, ou veja como o 5.1 trata o curl em O primeiro dia.
3. Apagar a sobreposição
É o mesmo POST do passo 1, com override_callback_uri em branco.
O que isso muda do nosso lado
Sobrepor o endereço passa a entrega do seu número para o seu servidor. No plano da API isso não tira nada de você, porque a Zera não usa esses eventos para nada nesse plano. Três consequências que valem estar escritas:
- Se um dia você contratar a caixa de entrada da Zera para esse mesmo número, essa sobreposição precisa sair antes da troca de plano, senão as mensagens continuam indo para o seu servidor e não aparecem no atendimento. Se foi o painel que apontou, apague o endereço no próprio painel, salvando o campo em branco, antes de trocar de plano: depois da troca o bloco do webhook some, o salvamento passa a responder
plano_com_inboxaté para apagar, e a troca de plano não mexe na sobreposição. Se você apontou a mão, apague pelo caminho manual. Nos dois casos, avise a Zera antes. - Se você mudar de servidor, o caminho normal é o painel. Ele reusa o código de verificação que guardou e grava o estado verdadeiro.
- Se você apontou a mão, o painel não fica sabendo. A linha de estado dele mostra o desfecho do último apontamento que ele fez ou tentou, guardado no nosso banco, e não relê a Meta quando você abre a tela. E se o painel tiver outro endereço guardado, o próximo salvamento pelo painel aponta aquele endereço por cima do seu, e a rotina de operação da Zera pode fazer isso sem salvamento nenhum enquanto esse endereço não for o último que o painel confirmou na Meta, no
phone_number_idatual do número. Avise a Zera de que você está no caminho manual. Informar no painel o endereço que você apontou deixa os dois lados iguais, com a consequência descrita no começo desta seção: o painel aponta de novo, com o código dele.
O primeiro contato: a verificação
Depois que o endereço é apontado, a Meta bate no seu servidor com um GET, e não com um POST. Se isso acontece no mesmo instante ou depois, a Zera ainda não mediu. Ela manda três parâmetros de consulta, e o hub.verify_token é o código de verificação daquele número que o painel devolveu, ou o que você escolheu no caminho manual:
GET https://seusistema.com.br/whatsapp
?hub.mode=subscribe
&hub.verify_token=O_CODIGO_DE_VERIFICACAO_DESTE_NUMERO
&hub.challenge=1158201444
O seu servidor precisa fazer três coisas, nesta ordem:
- Conferir que
hub.verify_tokené exatamente um dos códigos de verificação que você guardou. O código é de cada número, e não da conta: se o mesmo endereço recebe mais de um número, ele precisa aceitar o código de cada um, porque oGETde verificação não diz de qual número veio. A ficha de um segundo número já vem com o endereço do primeiro preenchido. Se não for nenhum deles, responda 403 e pare. - Responder 200 com o valor de
hub.challengeno corpo, em texto puro. Sem aspas, sem JSON, sem quebra de linha a mais. - Não responder mais nada nesse contato.
Se essa resposta não sair certa, a Meta não fecha a assinatura e nenhum evento chega depois. É o ponto onde mais gente trava, e o sintoma é o pior de todos: nada acontece e nada dá erro.
Deixe o seu servidor no ar respondendo a esse GET antes de salvar o endereço no painel. A Zera não conseguiu determinar se a Meta dispara essa verificação no mesmo instante do apontamento ou depois, e não vai afirmar uma coisa nem outra. O estado que o painel mostra vem da releitura do que ficou configurado, e não de alguém ter sido verificado: ou seja, o painel pode dizer Apontado na Meta com o seu servidor ainda mudo. Quem fecha a assinatura é a sua resposta ao hub.challenge.
Na primeira vez, o seu servidor ainda não conhece o código, porque ele nasce na primeira tentativa de apontar: o seu salvamento, ou a rotina de operação da Zera para um endereço guardado antes da conexão. O mesmo vale para cada número novo que você apontar para o mesmo endereço. Como atravessar isso, e o risco de cada jeito, está em Em ordem, do zero e em Para o número seguinte no mesmo endereço.
O que chega
A partir daí, tudo vem por POST, com Content-Type: application/json. O formato abaixo é o que a Meta documenta para a Cloud API, e é o mesmo que a plataforma da Zera recebe nos clientes com caixa de entrada. Ele não foi capturado do seu número.
Uma mensagem de texto recebida
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "WABA_ID",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "5511999999999",
"phone_number_id": "PHONE_NUMBER_ID"
},
"contacts": [
{ "profile": { "name": "Ana" }, "wa_id": "5511987654321" }
],
"messages": [
{
"from": "5511987654321",
"id": "wamid.HBgN...",
"timestamp": "1755990000",
"type": "text",
"text": { "body": "Bom dia, o pedido saiu?" }
}
]
}
}
]
}
]
}
Uma confirmação de entrega
É por aqui, e só por aqui, que você fica sabendo se a mensagem chegou no aparelho. A resposta do envio devolve accepted, que quer dizer aceita pela Meta, nunca entregue.
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "WABA_ID",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "5511999999999",
"phone_number_id": "PHONE_NUMBER_ID"
},
"statuses": [
{
"id": "wamid.HBgN...",
"status": "delivered",
"timestamp": "1755990005",
"recipient_id": "5511987654321"
}
]
}
}
]
}
]
}
O id aqui é o mesmo wamid que voltou no envio. É ele que amarra o que você mandou com o que aconteceu depois. Guarde o wamid de todo envio: sem ele, entrega e leitura chegam sem dono.
Cinco regras de quem recebe
- Responda 200 antes de processar. Grave o corpo, responda, e faça o trabalho depois, fora da requisição. Webhook que demora vira webhook reentregue.
- A Meta tenta de novo. Se o seu servidor cair ou responder erro, o evento volta mais tarde. Isso é bom, e é também o motivo da próxima regra.
- Trate repetição. O mesmo evento pode chegar duas vezes. Use o
idda mensagem como chave: se já existe, ignore. Sistema que não faz isso responde duas vezes para o mesmo cliente. - Não conte com a ordem. Pode chegar
readantes dedelivered. Grave o estado mais avançado, e não o último que chegou. - Um
POSTpode trazer vários eventos.entry,changesemessagessão listas. Percorra as três.
Como conferir que veio mesmo da Meta
Todo POST chega com o cabeçalho X-Hub-Signature-256, que é um HMAC SHA-256 do corpo cru, byte a byte, antes de qualquer leitura de JSON. Como diz a faixa do começo desta página, você não consegue validar essa assinatura: o segredo que a calcula é o do aplicativo da Zera, é o mesmo para todos os clientes e não vai na credencial. Ligar a escrita do webhook na Meta não mudou isso.
As quatro defesas que restam, e todas valem a pena:
- Um endereço que ninguém adivinha. Um caminho longo e aleatório no seu servidor, em
https, e nada de/webhookna raiz do domínio. - O código de verificação. Ele é gerado por nós, tem 32 bytes aleatórios e é único deste número. Recuse todo
GETde verificação que não trouxer exatamente um dos códigos de verificação dos números que apontam para aquele endereço: cada número tem o seu. - Casar o
wamid. Trate todo corpo recebido como não confiável até ligar o identificador da mensagem a um envio ou a uma conversa que você conhece. - Não agir só com base no corpo. Se um evento dispara alguma coisa cara do seu lado, confirme na Graph antes, com a sua credencial.
Se a validação de assinatura for exigência do seu lado, escreva para [email protected]. É um caso que a Zera quer conduzir junto, e não um caso para você contornar sozinho.
Onde o endereço fica guardado do nosso lado
O painel mostra o seu endereço de webhook na aba Detalhes do número, no cartão Ajustes de API. Ele entra pela primeira vez na ficha do número, e não no formulário de contratação: é o formulário que você responde depois de comprar, com os dados daquele número. Lá o campo é opcional de propósito, e só aparece no plano sem caixa de entrada, porque metade de quem compra a credencial crua ainda está desenvolvendo e não tem endereço para dar.
Você troca o endereço no próprio painel, desde 24 de agosto de 2026: aba Detalhes do número, cartão Ajustes de API, no bloco O endereço para onde os eventos deste número devem ir. Desde 23 de setembro de 2026, com a conexão do número concluída, esse salvamento também aponta na Meta, e é isso que esta página descreve. O bloco aparece no plano sem caixa de entrada, que é o mesmo plano em que o campo da ficha aparece.
O que o bloco escreve acima do campo depende do seu número. Com a conexão concluída: Salvar aponta este endereço na Meta; a linha abaixo diz o que ela confirmou. Com a conexão ainda por concluir: Por enquanto salvar só guarda: conclua a conexão do número e salve de novo. Até 23 de setembro de 2026 o bloco dizia, para todo mundo, que a entrega na Meta era configurada por você.
Em ordem, do zero
Na primeira vez, o código de verificação ainda não existe em lugar nenhum. Ele nasce na primeira tentativa de apontar o endereço, e só aparece na resposta quando essa tentativa é um salvamento seu que fecha, então o seu servidor não tem como conferi-lo antes. E isso se repete a cada número, porque cada um tem o seu código: a lista abaixo é a do primeiro, e a do número seguinte está logo depois dela, em Para o número seguinte no mesmo endereço. São dois jeitos de atravessar a primeira vez. O primeiro é pelo painel:
- Conclua a conexão do número. Sem ela, salvar o endereço só guarda. Se você já informou o endereço na ficha do número, deixe o servidor do passo 2 no ar antes de concluir a conexão: depois dela, a rotina de operação da Zera pode apontar esse endereço sem você salvar nada.
- Deixe o seu servidor no ar, em
https, respondendo aoGETde verificação com ohub.challengeem texto puro, ainda sem conferir ohub.verify_token, só enquanto este número não tiver o código dele na sua lista. - Entre no painel com o e-mail do cadastro, para a sessão ter menos de dez minutos.
- Em Canais, abra o número, aba Detalhes, cartão Ajustes de API, e salve o endereço no bloco do webhook.
- Copie o código de verificação, se ele aparecer na resposta, acrescente à lista de códigos aceitos do seu servidor e ligue a conferência: a partir daqui, todo
GETde verificação com um valor fora da sua lista de códigos recebe 403. Guarde como lista desde o primeiro número, porque cada número tem o seu código. Se ele não aparecer com a linha dizendo Apontado na Meta, ele foi gerado antes, numa tentativa sua ou numa reaplicação feita pela operação da Zera. Com uma recusa, ele pode também ter sido gerado agora, porque a resposta de um apontamento que não fechou não o traz. Nos dois casos, pegue no campowebhook_verify_tokenda exibição da credencial, ligue a conferência e, se a linha trouxe recusa, salve o endereço de novo, que o painel reusa o mesmo código. - Confira a linha de estado: ela tem de dizer Apontado na Meta, com data e hora.
- Mande uma mensagem do seu celular para o número conectado e confira que o corpo chegou no seu servidor.
O risco do passo 2, dito inteiro. Enquanto o seu servidor responde sem conferir o código, qualquer um que conheça o endereço consegue uma verificação aceita por ele. Como você também não valida a assinatura dos eventos, o que segura essa janela é o endereço que ninguém adivinha, descrito em Como conferir que veio mesmo da Meta. Mantenha a janela em minutos: abra logo antes de salvar e feche assim que o código aparecer.
O segundo jeito não tem janela nenhuma: faça o primeiro apontamento pelo caminho manual, com a sua credencial e um código escolhido por você, que o seu servidor confere desde o primeiro segundo. O preço é o descrito lá: o painel não conhece esse código, e no dia em que você salvar o endereço pelo painel ele aponta de novo com o código dele. A rotina de operação da Zera pode fazer o mesmo sem você salvar nada, se o painel guardar um endereço que ainda não confirmou na Meta, como o da ficha. Nesse dia você passa pelos passos 2 a 5 da lista acima, com o código novo.
Para o número seguinte no mesmo endereço
O código de verificação é de cada número, e não da conta. A ficha do segundo número já vem com o endereço do primeiro preenchido, e o apontamento dele gera um código novo, diferente do que está no seu servidor. Um servidor que aceita um código só responde 403 à verificação do segundo número, a Meta não fecha a assinatura dele, e esse número fica sem receber nada, sem erro em lugar nenhum. A ordem que evita isso:
- Antes de concluir a conexão do número seguinte, e antes de salvar o endereço dele no painel, deixe o seu servidor pronto para aceitar o código novo: por exemplo, aceitar por alguns minutos um código que ainda não está na sua lista, só durante a configuração. A conexão também entra nessa ordem porque, com o endereço já guardado pela ficha, a rotina de operação da Zera pode apontá-lo sem você salvar nada.
- Entre no painel com o e-mail do cadastro, para a sessão ter menos de dez minutos.
- Em Canais, abra o número seguinte, aba Detalhes, cartão Ajustes de API, e salve o endereço no bloco do webhook desse número.
- Pegue o código novo na resposta desse salvamento ou no campo
webhook_verify_tokenda exibição da credencial daquele número, e nunca na do primeiro. - Acrescente o código novo à sua lista, sem tirar o do primeiro número, e feche a janela: volte a responder 403 a todo
GETde verificação com um valor fora da sua lista de códigos. - Confira que a linha de estado desse número diz Apontado na Meta, e mande uma mensagem do seu celular para ele.
A janela do passo 1 tem o mesmo risco do passo 2 da primeira vez, e a mesma defesa: o endereço que ninguém adivinha. Mantenha a janela em minutos.
Se qualquer passo travar, o caminho manual destrava sem depender do painel.