Ir para o conteúdo
32 min read

Conectando pela Meta Graph API

Aponte uma integração existente com a WhatsApp Cloud API para o WizMessage sem reescrevê-la — os caminhos da Meta, os corpos de requisição da Meta, o envelope de erro da Meta, webhooks no formato da própria Meta e os endpoints de gerenciamento da conta para templates, perfil comercial, QR codes e flows.

Conectando pela Meta Graph API

Há duas formas de enviar mensagens de WhatsApp pelo WizMessage. A API do WizMessage é a indicada se você está começando do zero — ela oferece variáveis de template com nome, uma interface de mapeamento e um campo reference que você pode usar como chave dos seus próprios registros.

Esta página documenta a outra. Ela espelha a WhatsApp Cloud API da Meta com fidelidade suficiente para que o código já escrito para a Meta funcione aqui depois de trocar duas coisas: a URL base e o token. Mesmos caminhos, mesmos corpos de requisição, mesmos corpos de resposta, mesmo envelope de erro. Se você já tem uma integração, uma biblioteca ou um desenvolvedor que conhece a Cloud API, este é o caminho mais curto.

Ela não é só uma superfície de envio. Junto com as mensagens, traz os endpoints de gerenciamento da conta — criar e apagar templates, ler e reescrever o perfil comercial, QR codes, configurações de comércio, a lista de bloqueio, configurações do telefone e flows — todos nos caminhos da própria Meta. Gerenciando a conta é o catálogo, e O que não está disponível aqui é a outra metade, igualmente importante.

As duas superfícies usam as mesmas chaves de API, as mesmas permissões e os mesmos limites de uso. Nada está obsoleto — escolha por integração, e você pode usar as duas ao mesmo tempo.

Qual escolher

A API do WizMessage comparada com a API compatível com a Meta GraphEscolha a que combina com o que você já temAPI do WizMessage/api/v1/externalvariáveis de template com nomeinterface de mapeamento no painelcampo reference, consulta de statusnosso próprio envelope JSONideal para começar do zeroMeta Graph API/v26.0/{phone-number-id}/…corpos de requisição da Metaenvelope de erro da Metagerencie templates e perfilwebhooks no formato da Metaideal se você já tem código da Cloud APIMesmas chaves, mesmas permissões, mesmos limites de uso. Use as duas se quiser.
Duas superfícies, uma conta. Nenhuma delas vai desaparecer.

O básico

URL base e a versão no caminho

https://wizmessage.com/v26.0/{phone-number-id}/messages

O segmento da versão é real e obrigatório, mas o valor dele não muda nada. Nós o reconhecemos como v{major}.{minor} e depois o ignoramos — a plataforma decide com qual versão do Graph conversa do outro lado.

Duas consequências que vale conhecer:

  • Fixar uma versão antiga continua funcionando. Quando a Meta aposentar a versão que o seu código tem fixa, as chamadas aqui seguem normalmente. Nada a agendar.
  • v26 sozinho é rejeitado. O número menor não é opcional. v26.0 funciona, v26 dá 404.

O {phone-number-id} precisa ser o número de WhatsApp por trás da chave de API que você está usando. Qualquer outro é recusado — veja Quando um id não é seu.

Autenticação

Envie sua chave de API como token Bearer, exatamente como você enviaria um token de acesso à Meta.

HeaderValor
AuthorizationBearer pk_live_… — sua chave de API completa, não o prefixo exibido no painel.

O X-API-Key também é aceito aqui, então uma chave que já funciona na API do WizMessage não precisa de nenhuma mudança de header. O Bearer é a forma preferida: é o que um SDK da Meta sem modificações envia, e esse é justamente o objetivo desta superfície.

Nota: "Require signature" precisa estar desativado na chave. Aqui isso não é uma preferência. Uma chave com essa opção ativada não consegue salvar um webhook da Meta Graph — o painel recusa com "Signed-request enforcement is not compatible with the Graph format — disable it on this key first" — e toda chamada sem assinatura que ela fizer nesta superfície é rejeitada como code 190 com um 401. Um cliente da Cloud API pronto não consegue produzir a nossa assinatura de requisição, então as duas opções são mutuamente exclusivas por design.

Sua assinatura

O módulo de API exige uma assinatura Wiz Bot ou Wiz Pro ativa. Isso vale para as duas superfícies — a API do WizMessage e esta — e é verificado em toda chamada, não só na criação da chave.

Se a assinatura vencer, a API não para naquele instante. A primeira chamada depois do vencimento inicia uma janela de sete dias e é liberada, e toda resposta durante essa janela traz um header:

HeaderValor
X-Subscription-WarningSubscription inactive. API access ends in 5 day(s).

Depois da janela, as chamadas são recusadas com o code 10 do Graph em 403:

{
	"error": {
		"message": "(#10) Access denied. The API module requires an active Wiz Bot or Wiz Pro subscription. Renew at /subscriptions to restore access.",
		"type": "OAuthException",
		"code": 10,
		"fbtrace_id": "AKnBaXsYl7FlmMVw3ql5Ywo"
	}
}
O que acontece com o acesso à API depois que a assinatura venceuma chave de APInormalsem headerjanela de tolerância7 dias, as chamadas continuam funcionandoX-Subscription-Warning em toda respostarecusadocode 10 em 403primeira chamada após o vencimentoo relógio começa aquifim da janelasem mais avisosRenovar a qualquer momento restaura o acesso em poucos minutos.
O vencimento em si não muda nada. O relógio começa na sua próxima chamada.

Três detalhes que decidem se você percebe a tempo:

  • O relógio começa na sua primeira chamada, não na data da cobrança. Uma chave parada por quinze dias depois do vencimento ainda recebe os sete dias completos, contados a partir da próxima vez que ela chamar.
  • A janela é por chave de API, não por conta. Cada chave tem o seu próprio relógio, a partir da sua própria primeira chamada, então a chave que você usa para testar e a que roda em produção vão expirar em dias diferentes. Confira o header na chave que importa.
  • A recusa chega sem aviso na requisição que a provoca. O X-Subscription-Warning é o único aviso antecipado que existe — registre-o em log, ou a primeira coisa que você vai descobrir é um 403.

Nota: A renovação leva alguns minutos para valer, não segundos. A resposta de direito de uso fica em cache por cinco minutos e o status da assinatura por trás dela por mais cinco, então uma renovação feita enquanto uma integração está falhando não vai resolver na tentativa seguinte. Espere alguns minutos antes de concluir que não funcionou.

Enviando uma mensagem

POST /{version}/{phone-number-id}/messages, com o corpo da Meta. Não o validamos além de conferir que é uma mensagem de WhatsApp — a Meta é a autoridade sobre o que é uma mensagem válida, e os erros dela são melhores do que qualquer um que pudéssemos inventar.

POST https://wizmessage.com/v26.0/106540352242922/messages

{
	"messaging_product": "whatsapp",
	"recipient_type": "individual",
	"to": "919876543210",
	"type": "text",
	"text": { "preview_url": false, "body": "Your order has shipped." }
}

Nota: O to recebe apenas dígitos aqui — 919876543210, sem +. Esse é o formato da Meta, e é a coisa que mais se erra ao alternar entre as duas superfícies: a API do WizMessage exige +E.164 e rejeita qualquer outra coisa, enquanto esta repassa o seu valor direto à Meta. A única exceção é POST /messages/contacts, que aceita as duas formas justamente para que um corpo do Graph passe sem alterações.

O envio de um template também segue o formato da Meta — componentes posicionais, não variáveis com nome. Não existe camada de mapeamento nesta superfície, e esse é o preço que você paga pela compatibilidade.

{
	"messaging_product": "whatsapp",
	"to": "919876543210",
	"type": "template",
	"template": {
		"name": "payment_failed_alert",
		"language": { "code": "en" },
		"components": [
			{
				"type": "body",
				"parameters": [
					{ "type": "text", "text": "Priya Sharma" },
					{ "type": "text", "text": "12 Aug 2026" },
					{ "type": "text", "text": "Wiz Pro" }
				]
			}
		]
	}
}

Enviando um cartão de contato

type: "contacts" é um vCard — um nome e o que mais você anexar. Sempre funcionou nesta superfície e continua funcionando, com o corpo da Meta inalterado.

POST https://wizmessage.com/v26.0/106540352242922/messages

{
	"messaging_product": "whatsapp",
	"recipient_type": "individual",
	"to": "919876543210",
	"type": "contacts",
	"contacts": [
		{
			"name": {
				"formatted_name": "Priya Sharma",
				"first_name": "Priya",
				"last_name": "Sharma"
			},
			"phones": [
				{ "phone": "+911122334455", "type": "WORK", "wa_id": "911122334455" }
			],
			"emails": [
				{ "email": "[email protected]", "type": "WORK" }
			],
			"org": { "company": "Acme Retail", "department": "Support", "title": "Account Manager" }
		}
	]
}

name.formatted_name é o único campo que o WhatsApp exige. O phones[].wa_id é o que vale acrescentar mesmo assim: com ele, o cartão traz um botão Message que abre uma conversa com aquela pessoa; sem ele, o cartão só pode ser salvo.

Esse mesmo corpo também é aceito em POST /messages/contacts na API do WizMessage — chaves do envelope, to só com dígitos e tudo. É o único endpoint daquela superfície que aceita esse formato, e ele existe porque as duas coisas que este caminho não consegue oferecer valem a pena: um envio aqui não é registrado sob um message_id, então não há status para consultar nem webhook message.sent para casar, e o corpo mal é verificado antes de chegar à Meta. Trocar a URL é a migração inteira.

Nota: Enviar vários cartões em uma mensagem é o array contacts da Meta, e a Meta aceita até 257 deles. O endpoint do WizMessage limita a dez — então um envio em massa de cartões que funciona aqui é um dos poucos corpos que não passam para o outro lado sem alterações.

Marcando uma mensagem como lida

Confirmações de leitura e indicadores de digitação são enviados ao mesmo endpoint, com um corpo que não traz nem to nem type. O markAsRead() de todo SDK da Cloud API depende disso funcionar, então é suportado exatamente como a Meta especifica.

POST https://wizmessage.com/v26.0/106540352242922/messages

{
	"messaging_product": "whatsapp",
	"status": "read",
	"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgUQ0Uz…"
}

A resposta é {"success": true} — não o envelope de envio abaixo. Ramifique conforme a chamada que você fez, em vez de tentar ler messages[0].id em toda resposta.

O que volta

A resposta da Meta, sem alterações:

{
	"messaging_product": "whatsapp",
	"contacts": [{ "input": "919876543210", "wa_id": "919876543210" }],
	"messages": [{ "id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgUQ0Uz…" }]
}

Esse wamid é a sua única referência à mensagem. Esta superfície deliberadamente não tem campo reference nem endpoint de consulta de status — os dois são acréscimos do WizMessage que a API da Meta não tem, então incluí-los quebraria a compatibilidade para a qual esta superfície existe.

Para acompanhar o destino de uma mensagem, case o wamid com os status de entrega que chegam ao seu webhook. Se você preferir ter um campo seu para usar como chave, é para isso que serve o reference da API do WizMessage.

Erros

Os erros voltam no envelope da Meta, nunca no nosso. De onde veio o erro define o quanto dele é nosso.

Como um erro chega até você dependendo de quem o produziuUm erro nesta superfícieA Meta rejeitourepassado literalmentecode e subcode da Metafbtrace_id da própria Metao status também é o da MetaNós rejeitamosautenticação, permissão, limite de usoescrito nos códigos da Metapara um só tratador cobrir os dois401 · 403 · 429 · 400 · 500Nunca presuma que type é OAuthException — a Meta usa vários.
Os erros da própria Meta são repassados sem alterações. Os nossos usam os códigos da Meta.

Erros que nós geramos

codeHTTPQuando
190401Chave ausente, desconhecida, revogada, suspensa ou expirada — ou uma chamada sem assinatura para uma chave que exige assinaturas. Também gerado quando a própria conta de WhatsApp perdeu a conexão com a Meta; veja a nota abaixo.
10403A chave não tem a permissão de que esta chamada precisa (veja Permissões), ou a assinatura da conta venceu além da janela de tolerância.
4429O limite por minuto ou diário da chave foi esgotado.
100400Requisição malformada, um id no caminho que não é seu ou um endpoint que esta superfície não atende.
2500Algo falhou do nosso lado. Tente de novo.

Nota: Duas coisas diferentes retornam 190, e elas pedem correções opostas. Se a mensagem menciona a chave, troque ou digite novamente a chave. Se ela diz "the connection to this WhatsApp Business Account needs to be renewed", a chave está boa e a conta perdeu o vínculo com a Meta — reconecte-a pelo painel. Trocar uma chave que funciona em resposta à segunda situação faz você perder uma tarde e não muda nada.

Erros que a Meta gera

Repassados exatamente como recebidos, com o status HTTP da Meta. Tudo o que a Meta enviou sobrevive — error_subcode, error_data.details e o fbtrace_id da própria Meta:

{
	"error": {
		"message": "Unsupported post request. Object with ID '106540352242922' does not exist, cannot be loaded due to missing permissions, or does not support this operation.",
		"code": 100,
		"type": "GraphMethodException",
		"error_subcode": 33,
		"fbtrace_id": "AKnBaXsYl7FlmMVw3ql5Ywo"
	}
}

Nota: Repare no type desse exemplo — GraphMethodException, não OAuthException. A Meta usa vários tipos de exceção e nós não os normalizamos. Decida pelo error.code, que é um número e é estável; trate o type como um rótulo para humanos.

Quando um id não é seu

Todo id no caminho — o id do número de telefone, o id da WABA, um id de mídia — é verificado em relação à conta por trás da sua chave antes de qualquer outra coisa acontecer. Uma divergência retorna o código 100 redigido do jeito que a Meta redige um objeto que você não pode ver:

Unsupported post request. Object with ID '…' does not exist, cannot be loaded due to missing permissions, or does not support this operation.

Essa redação é proposital do lado da Meta e foi copiada propositalmente aqui: um id que existe mas pertence a outra pessoa e um id que não existe de forma alguma precisam ser indistinguíveis, senão o erro vira uma forma de descobrir os números de telefone de outras pessoas.

Mídia

Quatro endpoints, iguais aos da Meta.

MétodoCaminhoO que faz
POST/{version}/{phone-number-id}/mediaEnvia um arquivo. Multipart, retorna {"id": "…"}.
GET/{version}/{media-id}O objeto de metadados da Meta para o arquivo.
GET/{version}/{media-id}/downloadOs bytes.
DELETE/{version}/{media-id}Apaga o arquivo, retorna {"success": true}.
Enviar um arquivo uma vez e mandá-lo repetidamentePOST /{v}/{pnid}/mediao arquivo em si{ id }reutilizávelPOST /{v}/{pnid}/messagesreferencie o idEntregueos status chegam por webhook
O envio retorna um id. O id é o que você coloca em uma mensagem.

A única diferença proposital

GET /{version}/{media-id} retorna o objeto de metadados da Meta — messaging_product, mime_type, sha256, file_size, id — mas a url aponta para nós, não para o host lookaside da Meta.

Precisa ser assim. A URL de mídia da Meta só pode ser baixada com o token comercial da conta de WhatsApp, que fica do nosso lado e nunca sai de lá. Então entregamos uma URL que você consegue baixar: o endpoint de download acima, no mesmo host e no mesmo segmento de versão que você chamou, autenticado com o mesmo token Bearer que você já enviou.

Os clientes tratam essa URL como opaca e simplesmente fazem um GET nela com o seu token, e é por isso que um SDK sem modificações não percebe. Se o seu código trata o nome do host como caso especial, esse é o único ponto que vai precisar de mudança.

Permissões

Os mesmos chips de permissão da API do WizMessage, nas mesmas chaves. De onde a permissão é lida depende da chamada: o endpoint de envio a deduz do corpo da requisição, porque a Meta coloca todos os tipos de mensagem em um só caminho; em todos os outros, ela vem do caminho e do método.

Envio e mídia:

ChamadaPermissão
POST /{v}/{pnid}/messages com "type": "template"send_template
igual, com type de image, video, audio, document ou stickersend_media
igual, qualquer outro — texto, interactive, reaction, location, contacts, e marcar como lidasend_text
POST /{v}/{pnid}/media — envio de arquivosend_media
DELETE /{v}/{media-id}send_media
GET /{v}/{media-id} e /downloadview_messages

Gerenciamento da conta — veja Gerenciando a conta:

ChamadaPermissão
GET /{v}/{waba-id}/message_templatesview_templates
POST e DELETE /{v}/{waba-id}/message_templatesmanage_templates
GET /{v}/{waba-id}/template_analyticsview_analytics
GET em perfil comercial, QR codes, configurações de comércio, lista de bloqueio, configurações do telefone e flowsview_templates
POST nesses mesmos seismanage_profile
DELETE /{v}/{pnid}/message_qrdls/{code} e DELETE /{v}/{pnid}/block_usersmanage_profile

Esses são os únicos dois DELETE do lado do gerenciamento. Não há delete no perfil comercial, nas configurações de comércio, nas configurações do telefone nem nos flows — um DELETE em qualquer um deles é recusado como code 100, igual a um endpoint que não existe.

Nota: O envio de mídia exige send_media aqui. Na API do WizMessage ele não exige permissão nenhuma, com o raciocínio de que send_media controla o envio de um arquivo a um cliente, e não colocar um arquivo nos servidores da Meta. Esta superfície adota a linha mais rígida. Se você está levando uma chave de um lado para o outro e os envios de arquivo começam a retornar 403, é por isso.

Nota: view_templates controla muito mais do que templates. Ler o perfil comercial, os QR codes, as configurações de comércio, a lista de bloqueio, as configurações do telefone e os flows depende dele. O nome é histórico — é anterior a tudo o que está na segunda tabela. Se você o desmarcar ao restringir uma chave, seis leituras sem relação começam a retornar 403 com uma mensagem sobre templates, o que não é uma pista útil.

Nota: Três chips existem só na Meta Graph. manage_templates, manage_profile e view_analytics não controlam nada na API do WizMessage — existem para os endpoints desta página. Toda chave que já existia recebeu os três, então nada que funcionava deixou de funcionar; restringir é desmarcar de propósito no formulário de edição da chave.

Uma recusa é o code 10 do Graph em 403 — nunca o nosso PERMISSION_DENIED. Um único tratador de erros cobre esta superfície inteira.

A propriedade é verificada antes da permissão. Se o id no caminho não é o que está por trás da sua chave, a resposta é code 100 e ela nunca revela qual permissão estava faltando — senão o próprio erro viraria uma forma de sondar as contas de outras pessoas.

Gerenciando a conta

Além de enviar, esta superfície traz os endpoints de gerenciamento da conta da Meta, nos caminhos da própria Meta. São oito recursos, e a permissão de que você precisa depende de estar lendo ou escrevendo.

Os endpoints de gerenciamento da conta agrupados pela permissão que cada um exigeLer qualquer coisaos oito recursosperfil, QR codes, comérciolista de bloqueio, configurações, flowstemplatesview_templatesEscrever na contaperfil e presençaperfil comercial, QR codescomércio, lista de bloqueioconfigurações, flowsmanage_profileEscrever templatescriar e apagarmessage_templatesas análises são separadasmanage_templatesA análise de templates tem o seu próprio chip: view_analytics.
Ler é um chip só. Escrever se divide conforme o que você escreve.

Três coisas valem para todo endpoint desta seção, e são a razão inteira de ela poder existir:

  • O corpo da requisição é o da própria Meta, repassado sem alterações e sem validação. Não redocumentamos os corpos da Meta aqui e não os verificamos. A referência da Meta é a especificação, e o erro da Meta é o que volta se você errar um — que é um erro melhor do que qualquer um que pudéssemos inventar.
  • O corpo é enviado apenas em POST. Um corpo anexado a um DELETE é descartado antes de a requisição sair de nós. O delete de template da Meta recebe ?name= ou ?hsm_id= como parâmetros de query, não como corpo, então funciona como documentado; qualquer outra coisa que espere um corpo no delete não vai funcionar.
  • Sua query string é repassada, menos access_token e appsecret_proof. Esses dois são removidos porque a Meta dá preferência a um token na query string em vez do header Authorization, e deixá-los passar tornaria o seu Bearer opcional. Um cliente que se autentica pela query string vai ver isso ignorado em silêncio — use o header.

Nota: O id da WABA e o id do número de telefone nesses caminhos precisam ser os que estão por trás da sua chave. Isso é verificado antes de qualquer coisa sair da nossa rede, e uma divergência é o mesmo code 100 descrito em Quando um id não é seu.

Nota: O paging.next aponta para a Meta, não para nós. As respostas voltam exatamente como a Meta as enviou, então um link next em uma resposta paginada é uma URL de graph.facebook.com — e a sua chave de API não autentica lá. Pagine pegando o paging.cursors.after e refazendo a requisição neste host com ?after=.

Templates

GET    https://wizmessage.com/v26.0/{waba-id}/message_templates?limit=25
POST   https://wizmessage.com/v26.0/{waba-id}/message_templates
DELETE https://wizmessage.com/v26.0/{waba-id}/message_templates?name=order_shipped
MétodoPermissãoRetorna
GETview_templates{ data, paging } da Meta
POSTmanage_templatesA resposta de criação da Meta
DELETEmanage_templatesA resposta de exclusão da Meta

O GET aceita limit (1–100, padrão 25) e after para paginação por cursor.

Nota: O GET aceita apenas limit e after. Os outros filtros da Meta nesta edge — name, status, fields, language, category, name_or_content — são descartados em silêncio, não rejeitados. Uma requisição com ?status=APPROVED retorna um 200 com todos os templates, aprovados ou não. Filtre do seu lado, ou use after para paginar a lista completa. Este é o único ponto desta página em que um cliente portado recebe uma resposta errada em vez de um erro, então vale revisar o seu código.

Análise de templates

GET https://wizmessage.com/v26.0/{waba-id}/template_analytics

Exige view_analytics. A Meta exige start e responde code 100 — "(#100) The parameter start is required" — sem ele. granularity e os demais parâmetros são da Meta; passe-os e eles chegam sem alterações.

Perfil comercial

GET  https://wizmessage.com/v26.0/{phone-number-id}/whatsapp_business_profile?fields=about,address,description,email,profile_picture_url,websites,vertical
POST https://wizmessage.com/v26.0/{phone-number-id}/whatsapp_business_profile

O GET exige view_templates e retorna o { data } da Meta. O POST exige manage_profile.

QR codes

GET    https://wizmessage.com/v26.0/{phone-number-id}/message_qrdls
POST   https://wizmessage.com/v26.0/{phone-number-id}/message_qrdls
DELETE https://wizmessage.com/v26.0/{phone-number-id}/message_qrdls/{code}

O GET exige view_templates e retorna o { data } da Meta. O POST e o DELETE exigem manage_profile.

O caminho /{code} é somente DELETE. Não há GET nem POST em um código individual — leia a coleção e escolha dela.

Configurações de comércio

GET  https://wizmessage.com/v26.0/{phone-number-id}/whatsapp_commerce_settings
POST https://wizmessage.com/v26.0/{phone-number-id}/whatsapp_commerce_settings

O GET exige view_templates e retorna o { data } da Meta. O POST exige manage_profile.

Clientes bloqueados

GET    https://wizmessage.com/v26.0/{phone-number-id}/block_users
POST   https://wizmessage.com/v26.0/{phone-number-id}/block_users
DELETE https://wizmessage.com/v26.0/{phone-number-id}/block_users

O GET exige view_templates e retorna o { data } da Meta. O POST e o DELETE exigem manage_profile. Observe que desbloquear é um DELETE na própria coleção, não em um caminho por cliente.

Configurações do número de telefone

GET  https://wizmessage.com/v26.0/{phone-number-id}/settings
POST https://wizmessage.com/v26.0/{phone-number-id}/settings

O GET exige view_templates e retorna { calling, storage_configuration }. O POST exige manage_profile.

Nota: O caminho é /settings. Algumas referências de API de terceiros chamam isso de whatsapp_calling_settings — esse caminho não existe na Meta e retorna code 2500 aqui, porque nós o repassamos e a Meta o rejeita. Veja O que não está disponível aqui.

Flows

GET  https://wizmessage.com/v26.0/{waba-id}/flows
POST https://wizmessage.com/v26.0/{waba-id}/flows

O GET exige view_templates e retorna o { data, paging } da Meta. O POST exige manage_profile.

O que não está disponível aqui

Tudo o que está acima foi confirmado em uma conta de WhatsApp real. O que está abaixo também — e é por isso que esta seção consegue dizer por que um endpoint está ausente, e isso costuma ser o que decide o que você faz em seguida.

O que um endpoint indisponível retorna. Tudo o que não está na lista acima responde com a redação da própria Meta para um caminho que ela não atende — code 100 em 400:

Unsupported get request. Object with ID '…' does not exist, cannot be loaded due to missing permissions, or does not support this operation.

Um caminho que é suportado mas chamado com um método que ele não oferece retorna o mesmo código com uma mensagem mais curta: "Unsupported delete request." As duas são propositalmente indistinguíveis de um objeto que você simplesmente não consegue ver — um erro que confirmasse quais endpoints existem seria uma forma de mapear a superfície.

Por que um endpoint pode não estar disponível nesta superfícieum endpoint que não está aquiNós o recusamosregistration, subscribed_appsquebraria a sua contasem alternativaA Meta o recusanão existe em número de coexistênciaconversational_automation, callsa resposta da própria MetaEle não existequatro caminhos de espelhos de APIa Meta responde code 2500use o caminho realUm quarto grupo simplesmente ainda não está na nossa lista — veja abaixo.
Quatro motivos, e só um deles é nosso.

Recusado de propósito

Estes são bloqueados em todos os métodos, antes de qualquer coisa ser repassada:

CaminhoPor quê
register, deregister, request_code, verify_codeUm número em coexistência nunca deve ser registrado na Cloud API. Fazer isso é exatamente como uma conta passa a falhar em todo envio com o erro 133010, e não é reversível do seu lado.
subscribed_appsTodos os métodos, inclusive GET. Um DELETE aqui cancelaria a inscrição dos nossos webhooks na sua própria conta de WhatsApp e quebraria em silêncio a sua própria integração; também não oferecemos a leitura, para não deixar um caminho em que um deslize faça isso.
marketing_messagesOs envios de marketing passam pelas ferramentas de campanha do painel, que aplicam a taxa de envio por conta.
/{template-id}/unpauseA Meta identifica o template por um id solto, sem a conta no caminho, então não conseguimos provar que o template é seu antes de agir sobre ele. Retome pelo painel.
messages em qualquer método exceto POSTO caminho de envio carrega o limitador de taxa e o tratamento de suspensão de conta. Só o POST é roteado.

Indisponível em um número de coexistência

As respostas da própria Meta, conferidas em uma conta real em 10 de agosto de 2026:

CaminhoResposta da Meta
/{phone-number-id}/conversational_automationcode 100 — "Tried accessing nonexisting field"
/{phone-number-id}/callscode 100 — "Tried accessing nonexisting field"
/{phone-number-id}/groupscode 131000 em um 500

Essas são restrições da Meta para números que operam em coexistência com o app WhatsApp Business, não nossas — repassamos a requisição e é isso que volta. A Meta amplia a disponibilidade de tempos em tempos, então, se algum desses importa para você, vale testar de novo em vez de confiar nesta tabela indefinidamente.

Caminhos que não existem em lugar nenhum

Cada um destes aparece em referências de API de terceiros e não é um endpoint da Meta. Os quatro retornam code 2500, "Unknown path components":

O que você pode ter lidoO que usar no lugar
POST /{phone-number-id}/mark_message_as_readPOST /{phone-number-id}/messages com um corpo status: "read" — veja Marcando uma mensagem como lida
GET /{phone-number-id}/message_statusOs status de entrega chegam ao seu webhook; veja Escolhendo o que você recebe
/{phone-number-id}/whatsapp_calling_settings/{phone-number-id}/settings
/{phone-number-id}/conversational_componentsNada — o caminho real mais próximo é conversational_automation, que não está disponível em um número de coexistência

Real na Meta, mas fora da nossa lista

O GET /{waba-id}/phone_numbers funciona na Meta e não é bloqueado nem repassado aqui, então responde code 100. É um dos primeiros endpoints que um cliente da Cloud API chama, então vale citá-lo: a sua chave já fixa exatamente um número de telefone, e o id dele é o que você coloca em todo caminho desta página. Você pode lê-lo na aba Overview da chave, no painel.

Leituras de nó direto

O GET /{v}/{waba-id}?fields=… e o GET /{v}/{phone-number-id}?fields=… funcionam na Meta, mas não aqui. Um caminho de um único segmento nesta superfície é um id de mídia — é isso que GET /{v}/{media-id} significa —, então um id de WABA nessa posição é procurado como mídia e falha.

A falha é confusa se você não a espera: ela verifica view_messages primeiro, então uma chave sem essa permissão recebe um 403 que cita uma permissão sem relação alguma com o que foi pedido. Use as formas de edge no lugar — /{waba-id}/message_templates, /{phone-number-id}/settings e o resto de Gerenciando a conta.

Webhooks no formato da Meta

A aba de webhooks do painel abre com um formato de payload. WizMessage (v1) é o payload que a outra página documenta; é o que uma chave usa até você mudar.

A aba Webhooks com o formato do payload definido como WizMessage, mostrando as caixas de seleção de eventos da v1

Escolha Meta Graph e o seu endpoint deixa de receber o nosso payload {version, event, timestamp, data} e passa a receber o envelope que a Meta nos enviou, repassado literalmente — uma entrega por mudança.

{
	"object": "whatsapp_business_account",
	"entry": [
		{
			"id": "102290129340398",
			"time": 1770000123,
			"changes": [
				{
					"value": {
						"messaging_product": "whatsapp",
						"metadata": {
							"display_phone_number": "15550783881",
							"phone_number_id": "106540352242922"
						},
						"contacts": [{ "profile": { "name": "Priya Sharma" }, "wa_id": "919876543210" }],
						"messages": [
							{
								"from": "919876543210",
								"id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgg…",
								"timestamp": "1770000123",
								"type": "text",
								"text": { "body": "Has it shipped yet?" }
							}
						]
					},
					"field": "messages"
				}
			]
		}
	]
}

Se você já tem um tratador de webhook da Cloud API, esse é o formato para o qual ele foi escrito.

Trocar o formato também muda o que a aba pede: as caixas de seleção de eventos são substituídas pelos nomes de campo da própria Meta, e aparece um verify token.

A aba Webhooks no formato Meta Graph, mostrando o verify token e os nove nomes de campo da Meta que podem ser assinados

Antes de poder salvar um

Três coisas são necessárias, e o painel diz qual está faltando:

O que precisa estar pronto antes de um webhook da Meta Graph poder ser salvoWebhook secreté a chave da assinaturaVerify tokenprova que a URL é suaUma URL ativanós a chamamos antes de salvarSalvoas entregas começamUma URL que ainda não está respondendo não pode ser salva. Publique primeiro, configure depois.
As três são verificadas quando você aperta salvar, nesta ordem.
  • Um webhook secret. É a única coisa que dá a chave do X-Hub-Signature-256, então, sem ele, uma entrega ficaria sem assinatura e não verificável. Gere um na aba Webhooks da chave.
  • Um verify token. Qualquer texto que você escolher; o seu endpoint também precisa conhecê-lo.
  • Uma URL que já está respondendo. Veja abaixo.

O handshake de verificação

Quando você salva, chamamos a sua URL exatamente como a Meta chamaria:

GET https://your-server.example/webhooks
	?hub.mode=subscribe
	&hub.verify_token=<the token you set>
	&hub.challenge=482910337

O seu endpoint precisa conferir que o hub.verify_token é igual ao que você configurou e então responder 200 com o hub.challenge como o corpo inteiro da resposta — sem envelope JSON, sem aspas, sem mais nada. O challenge é um número de 9 dígitos, então um tratador que faz parseInt antes de devolvê-lo o devolve sem alterações.

Se o challenge não voltar exatamente igual, o salvamento é recusado e nada é armazenado. É isso que impede alguém de apontar a nossa fila de entrega para uma URL que não controla.

// express
app.get('/webhooks', (req, res) => {
	if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VERIFY_TOKEN) {
		return res.status(200).send(req.query['hub.challenge'])
	}
	res.sendStatus(403)
})

Verificando a assinatura

Toda entrega traz o X-Hub-Signature-256, calculado do jeito que a Meta o calcula: HMAC-SHA256 do corpo bruto da requisição, com a chave sendo o seu webhook secret, em hexadecimal, com o prefixo sha256=.

HeaderValor
X-Hub-Signature-256sha256= + HMAC-SHA256 do corpo bruto, com a chave sendo o seu webhook secret, em hexadecimal minúsculo.
X-Webhook-Eventmeta.<field> — qual campo esta entrega traz.
X-Webhook-IdId único desta tentativa de entrega. Deduplique por ele.
User-Agentfacebookplatform/1.0 (+http://developers.facebook.com)

Nota: Esta não é a mesma derivação do formato WizMessage. Aquele assina timestamp + "." + rawBody e envia o timestamp em um header próprio; este assina só o corpo, sem timestamp em lugar nenhum. Se você roda os dois formatos em um mesmo servidor, mantenha os dois verificadores separados — um verificador compartilhado vai falhar naquele para o qual não foi escrito.

Assine os bytes brutos como foram recebidos. Interpretar o JSON e serializá-lo de novo muda a ordem das chaves e os espaços, e a assinatura nunca vai bater.

const crypto = require('node:crypto')

// express: app.post('/webhooks', express.raw({ type: 'application/json' }), ...)
function verify(req) {
	const provided = req.get('X-Hub-Signature-256') || ''
	if (!provided.startsWith('sha256=')) return false

	const expected = crypto
		.createHmac('sha256', WEBHOOK_SECRET)
		.update(req.body) // the raw Buffer, not a parsed object
		.digest('hex')

	// Constant-time compare — a plain === leaks timing information.
	const a = Buffer.from(expected, 'hex')
	const b = Buffer.from(provided.slice('sha256='.length), 'hex')
	return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Escolhendo o que você recebe

O formato WizMessage faz você marcar eventos. Este faz você marcar os nomes de campo da Meta, iguais ao que você assinaria no App Dashboard da própria Meta.

CampoO que chega
messagesMensagens recebidas e status de entrega. Leia a nota abaixo.
message_statusStatus de entrega, como campo próprio.
smb_message_echoesMensagens que a empresa enviou pelo app WhatsApp Business no telefone dela.
smb_app_state_syncMudanças na agenda de contatos feitas nesse telefone.
user_id_updateA identidade de um contato no WhatsApp mudou.
message_template_status_updateUm template foi aprovado, rejeitado ou pausado.
message_template_quality_updateA pontuação de qualidade de um template mudou.
account_updateA conta de WhatsApp foi conectada, desconectada, restringida ou desvinculada.
historyConversas anteriores, durante a sincronização inicial. Payloads grandes — ative só se quiser.

Os novos webhooks do Graph começam inscritos apenas em messages. Desmarcar todos os campos desliga as entregas sem apagar a configuração.

Nota: O messages já traz os status de entrega. A Meta envia os dois em "field": "messages" — mensagens recebidas em um array messages, relatórios de entrega em um array statuses no mesmo objeto value. Então a inscrição padrão basta para correlacionar o wamid que você recebeu de um envio. O message_status é um campo adicional, não o que você precisa para o acompanhamento básico de status. Leia value.statuses antes de presumir que está faltando uma inscrição.

Três coisas que diferem do formato WizMessage

Vale ler se você está migrando uma integração, porque cada uma muda em silêncio o que aparece.

Três comportamentos que diferem entre os dois formatos de webhookSem trava do chatbotentregue de qualquer formaa v1 exige modo do bot = Webhooko Graph ignora essa configuraçãoVocê recebe sente status de campanhaa v1 filtra os doiso Graph repassa tudoSem sent sintéticoos eventos da própria Meta no lugarsmb_message_echoese statusEspere mais tráfego no formato Graph do que a mesma conta produzia na v1.
Nas três, o formato Graph entrega mais, não menos.
  • A entrega não depende do sistema de chatbot. No formato WizMessage, as mensagens recebidas só chegam ao seu endpoint quando o sistema de chatbot da conta está definido como Webhook. O formato Graph ignora essa configuração por completo — configurar uma URL e assinar um campo é a adesão inteira.
  • Você recebe status: "sent" e os status de campanha. O formato WizMessage descarta os dois de propósito. Aqui você recebe o que a Meta enviou, o que significa bem mais tráfego de status em uma conta que roda campanhas em massa.
  • Não existe message.sent sintético. O formato WizMessage fabrica um para as mensagens enviadas pela API, pelo painel ou pelo telefone. A Meta cobre o mesmo terreno com o smb_message_echoes e o status sent, então nada é inventado aqui.

Entrega, novas tentativas e desativação

Idêntico ao formato WizMessage — a mesma tabela vale aqui. Resumindo: esperamos 10 segundos, nunca seguimos redirecionamentos, tentamos de novo 5xx/408/429/timeouts cinco vezes com backoff, nunca tentamos de novo os outros 4xx e tratamos 410 Gone como "desative este webhook".

As entregas são at-least-once (pelo menos uma vez). Deduplique por X-Webhook-Id e responda 2xx rapidamente.

Testando sem escrever código

O diálogo Test API da chave tem um controle API Format. Defina como Meta Graph e o seletor de endpoint é substituído pelo alvo real da sua conta — POST /v26.0/{your phone number id}/messages — com um corpo no formato da Meta já preenchido.

O diálogo Test API no modo Meta Graph, mostrando o endpoint de mensagens somente leitura e um corpo de requisição no formato da Meta

"Simulate Only" devolve a requisição como eco, sem contatar a Meta; mudar para Send Real Message envia de verdade e mostra a resposta real da Meta, com o envelope de erro e tudo.

O diálogo mira POST /{phone-number-id}/messages e nada mais, então os endpoints de gerenciamento da conta não são alcançáveis por ele — teste esses com curl ou com o seu próprio cliente.

O Get code traz o mesmo controle, então o trecho gerado mira a superfície que você escolher, em qualquer uma das oito linguagens. Deixe "Require signature" desativado em uma chave que você pretende usar aqui — com ele ativado, o trecho Graph gerado também assina a requisição, que é a única coisa que um cliente da Cloud API sem modificações não consegue fazer.