Documentação
API Oficial do WhatsApp
Como ligar o número à Plataforma da Meta, enviar mensagens e modelos, e receber os eventos no seu sistema.
Atualizado a 12 de novembro de 2026
Índice
- Visão geral
- Começar
- 1. Ligar o número
- 2. Configurar o webhook e a chave
- 3. Usar o fluxo n8n pronto
- Autenticação
- Enviar mensagens
- Tipos de conteúdo
- Botões de resposta
- Janela de 24 horas e modelos
- Enviar um modelo
- Criar modelos
- Eventos
- message.received
- template.status
- connection.updated
- Verificar a assinatura
- Estado dos envios
- Ligar o estado à mensagem
- Erros
- Entrega dos eventos
- Limites do canal
- Custos
Visão geral
A API Oficial liga o seu número de WhatsApp à Plataforma da Meta (WhatsApp Cloud API) e dá-lhe uma API simples para enviar mensagens e receber o que os seus clientes escrevem. Funciona com o n8n, com o seu CRM ou com código seu.
O que recebe:
uma chave de acesso para enviar mensagens, modelos e consultar o estado dos números;
um webhook: cada mensagem recebida e cada mudança de estado chegam ao endereço que indicar, assinadas;
o portal do cliente, onde liga o número, cria modelos de mensagem e acompanha o consumo.
Todos os pedidos vão para:
https://wa.automate-ai.digitalAs mensagens são cobradas pela Meta, diretamente à sua conta (ver Custos). O plano cobre o número e a API.
Começar
1. Ligar o número
No portal, em Meus produtos, carregue em Ligar número oficial e siga o fluxo da Meta: escolhe ou cria a conta WhatsApp Business, confirma o número e autoriza a ligação. No fim, o número aparece como ligado.
Duas coisas que a Meta pede:
a verificação da empresa na Meta, para sair dos limites iniciais de envio;
um método de pagamento na conta WhatsApp Business: sem ele, a Meta deixa de enviar mensagens.
2. Configurar o webhook e a chave
No portal, em Integração técnica:
Webhook: indique o endereço que vai receber os eventos. O portal mostra uma vez o segredo do webhook, com o qual se verifica a assinatura. Guarde-o.
Chave de acesso: gere a chave. Também aparece uma única vez.
3. Usar o fluxo n8n pronto
Se usa n8n, no mesmo sítio há o botão Descarregar fluxo n8n. O fluxo já verifica a assinatura, responde às mensagens e envia modelos. Importe-o, ative-o, cole o URL de produção dele no passo Webhook, e cole o segredo e a chave onde o fluxo indica.
Autenticação
Todos os pedidos levam a chave no cabeçalho x-api-key:
curl https://wa.automate-ai.digital/instances/ID_DO_NUMERO \
-H "x-api-key: wak_xxxxxxxx_xxxxxxxx"Todas as respostas têm a mesma forma:
{ "ok": true, "data": { "...": "..." } }{ "ok": false, "error": { "code": "INSTANCE_NOT_FOUND", "message": "…", "retryable": false } }O ID_DO_NUMERO (instanceId) vem em cada evento que recebe no webhook.
Uma chave nova invalida a anterior no momento em que é gerada. Tudo o que usava a antiga deixa de funcionar até ser atualizado.
Enviar mensagens
curl -X POST https://wa.automate-ai.digital/instances/ID_DO_NUMERO/messages \
-H "x-api-key: $CHAVE" -H "content-type: application/json" \
-d '{
"to": { "kind": "contact", "id": "351912345678" },
"content": { "type": "text", "body": "Olá! Já tratámos do seu pedido." },
"idempotencyKey": "pedido-1234-confirmacao"
}'to.idé o número em formato internacional, sem+nem espaços.idempotencyKey(opcional, recomendado): se o mesmo pedido for repetido com a mesma chave, a mensagem não sai duas vezes.replyTo(opcional): oproviderMessageIdda mensagem a que responde, para aparecer como citação.
A resposta é 202 com o id da mensagem:
{ "ok": true, "data": { "messageId": "44444444-…", "queuedAt": "2026-11-12T10:00:00.000Z" } }202 quer dizer que a mensagem entrou na fila, não que foi entregue. O desfecho chega depois no webhook, como message.status (ver Estado dos envios).
Tipos de conteúdo
| Campos |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
media é { "kind": "url", "url": "https://…" }, ou { "kind": "base64", "data": "…", "mimeType": "image/png" }.
Botões de resposta
{
"to": { "kind": "contact", "id": "351912345678" },
"content": {
"type": "buttons",
"body": "Confirma a marcação de amanhã às 15h?",
"buttons": [
{ "id": "confirmar", "title": "Confirmar" },
{ "id": "remarcar", "title": "Remarcar" }
]
}
}Quando o cliente carrega num botão, recebe um message.received com content.type igual a interactive_reply e o mesmo id em replyId. É por ele que o seu fluxo decide o que fazer.
Janela de 24 horas e modelos
É a regra mais importante do canal oficial:
dentro de 24 horas desde a última mensagem do cliente, pode enviar mensagens livres (
/messages) e modelos;fora dessa janela, só modelos aprovados pela Meta (
/messages/template).
Uma resposta a uma mensagem que acabou de receber está sempre dentro da janela. Começar uma conversa (lembrete, confirmação, aviso) é sempre com modelo.
Cada message.received traz windowExpiresAt, o momento em que a janela fecha.
Uma mensagem livre fora da janela é aceite com 202 e falha depois, no webhook, com failureCode igual a WINDOW_CLOSED. A API não troca a mensagem por um modelo sozinha.
Enviar um modelo
curl -X POST https://wa.automate-ai.digital/instances/ID_DO_NUMERO/messages/template \
-H "x-api-key: $CHAVE" -H "content-type: application/json" \
-d '{
"to": { "kind": "contact", "id": "351912345678" },
"templateName": "pedido_confirmado",
"languageCode": "pt_PT",
"bodyParameters": ["Maria", "A-12"],
"buttonParameters": [{ "type": "url", "index": 1, "text": "A-12" }],
"idempotencyKey": "pedido-A-12-confirmado"
}'Campo | Para quê |
|---|---|
| o nome do modelo, como aparece no portal |
| o idioma do modelo ( |
| os valores de |
| o valor da variável do cabeçalho: texto, ou imagem |
| o valor de cada botão de link com parte variável |
Botão de link com parte variável. Num modelo com o link https://loja.pt/pedido/{{1}}, cada envio tem de dizer o que entra no {{1}}. index é a posição do botão no modelo, a contar de 0 e contando todos os botões (respostas rápidas e telefone incluídos). text é só a parte variável (A-12), nunca o link inteiro. Sem este valor, a Meta recusa o envio.
Criar modelos
No portal, no número oficial, separador Modelos, carregue em Criar modelo. São quatro passos: nome e categoria, texto com variáveis e exemplos, botões, e rever. A Meta analisa cada modelo, normalmente entre minutos e algumas horas.
Utilidade: avisos sobre algo que o cliente pediu (pedido, marcação, pagamento). É aprovado mais depressa.
Marketing: promoções e novidades.
Modelos com imagem no cabeçalho, carrossel ou autenticação criam-se no WhatsApp Manager da Meta. O portal tem um atalho para lá.
Quando a Meta decide, recebe um evento template.status (ver Eventos). Só um modelo com estado approved pode ser enviado.
Eventos
Cada evento chega ao seu webhook num POST com o corpo em JSON. Há quatro tipos:
| Quando |
|---|---|
| o cliente escreveu |
| um envio mudou de estado: |
| o estado do número mudou |
| um modelo foi aprovado, recusado, pausado… |
Ignore os tipos que não conhece, respondendo 2xx. A lista vai crescer, e um fluxo que assume "se não é A, então é B" trata um tipo novo como aquilo que ele não é.
message.received
{
"type": "message.received",
"eventId": "0f0f6ab7-2a2f-4f0b-9b6a-9a5f7d5d31c2",
"instanceId": "0d9b1b6f-6a2a-4a4c-9f66-3f6a3f0b6a2a",
"provider": "cloud_api",
"occurredAt": "2026-11-12T18:41:07.000Z",
"providerMessageId": "wamid.HBgM…",
"from": "351912345678",
"pushName": "Maria",
"content": { "type": "text", "body": "Olá, queria saber do meu pedido" },
"windowExpiresAt": "2026-11-13T18:41:07.000Z"
}Tipos de content:
| Campos |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
O storageUrl da mídia é temporário: serve para descarregar o ficheiro logo, não para guardar.
Se o seu número tiver o agrupamento de mensagens ligado, textos seguidos do mesmo cliente chegam num só evento, unidos por quebras de linha. Esse evento traz todos os ids em providerMessageIds.
template.status
{
"type": "template.status",
"instanceId": "0d9b1b6f-…",
"template": { "id": "806312974732579", "name": "pedido_confirmado", "language": "pt_PT", "category": "utility" },
"status": "rejected",
"reason": "invalid_format",
"rejection": { "reason": "…", "recommendation": "…" }
}status vem em minúsculas: pending, approved, rejected, paused, disabled, entre outros. A Meta acrescenta estados sem aviso: trate os que lhe interessam e ignore o resto. Uma conta com vários números recebe um aviso por número, e deduplica-se por template.id com status.
connection.updated
Traz status (connected, disconnected, logged_out…) e requiresReauth. Com requiresReauth: true, o número precisa de ser ligado outra vez no portal: a autorização da Meta deixou de valer.
Verificar a assinatura
Cada pedido do webhook traz:
x-timestamp: 1794519022
x-signature: sha256=9c1f…A assinatura é um HMAC-SHA256, com o segredo do webhook, sobre x-timestamp + "." + corpo cru. Para verificar:
recuse o pedido se faltar
x-signatureoux-timestamp;recuse se
x-timestamptiver mais de 5 minutos de diferença do relógio;calcule o HMAC sobre o corpo cru, byte a byte. Se voltar a serializar o JSON, a assinatura falha em alguns eventos;
compare com uma comparação de tempo constante.
const crypto = require("crypto");
function assinaturaValida(segredo, timestamp, corpoCru, assinatura, assinaturaNova) {
if (!assinatura || !timestamp) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const esperado = Buffer.from(
"sha256=" + crypto.createHmac("sha256", segredo).update(`${timestamp}.${corpoCru}`).digest("hex")
);
const bate = (h) => !!h && Buffer.from(h).length === esperado.length && crypto.timingSafeEqual(Buffer.from(h), esperado);
return bate(assinatura) || bate(assinaturaNova);
}Verifique antes de responder. Se a assinatura falhar, responda 401: o evento fica guardado do nosso lado e não se perde. Se responder 2xx antes de verificar, um evento recusado depois perde-se sem rasto.
Troca do segredo sem perder eventos. Durante uma troca, cada pedido traz também x-signature-next, assinado com o segredo novo. Aceite qualquer uma das duas assinaturas. Assim pode passar a usar o segredo novo quando quiser, sem uma janela em que os eventos falham.
Estado dos envios
| Quer dizer |
|---|---|
| a Meta aceitou a mensagem |
| a Meta aceitou e reteve a mensagem para avaliar a qualidade. Ainda não é sucesso nem falha |
| chegou ao telemóvel do cliente |
| o cliente leu |
| não saiu: |
Com held, espere pelo estado seguinte antes de confirmar o que quer que seja ao cliente.
Ligar o estado à mensagem
Os eventos de estado trazem messageId (o id que a API lhe devolveu), providerMessageId (o id da Meta), ou os dois. Nunca chegam sem nenhum.
POST /messages → { messageId: "4444…" }
message.status sent → { messageId: "4444…", providerMessageId: "wamid.X" } ← o par
message.status delivered → { providerMessageId: "wamid.X" }
message.status read → { providerMessageId: "wamid.X" }Guarde o providerMessageId que chega com o sent (ou o held). Os delivered e read seguintes trazem só esse.
Erros
Na resposta do pedido. São erros que se resolvem no próprio pedido:
HTTP |
| Quando |
|---|---|---|
400 |
| o corpo não está certo (a mensagem diz o quê) |
401 |
| chave em falta, errada ou revogada |
404 |
| o número não existe na sua conta |
No webhook, num message.status de failed. Tudo o que depende da Meta chega aqui, depois do 202:
| Quando | Onde ver |
|---|---|---|
| mensagem livre fora da janela de 24 horas | envie um modelo |
| o modelo não existe nesse idioma, não está aprovado, ou foi pausado | separador Modelos no portal |
| o número de variáveis não bate com o modelo, ou o texto ficou longo demais | o corpo do pedido |
| algo que este canal não faz (por exemplo, envio para grupo) | |
| o número não está ligado | portal |
| a Meta recusou por outro motivo | o texto em |
Entrega dos eventos
Responda 2xx logo que receber, antes de processar. Cada tentativa tem 10 segundos. Um endpoint que responde só no fim do fluxo arrisca receber o mesmo evento outra vez.
Deduplique por
eventId. Cada evento é entregue pelo menos uma vez e pode chegar repetido.Ordene por
occurredAt, não pela ordem de chegada. Umdeliveredpode chegar antes dosent.Repetições: com um 5xx, um 429 ou sem resposta, repetimos até 8 vezes ao longo de cerca de 30 minutos. Um 4xx (exceto 429) não é repetido: o evento fica guardado do nosso lado.
Limites do canal
O canal oficial não tem:
grupos: não envia nem recebe mensagens de grupos;
indicador "a escrever" nem apagar mensagens enviadas;
nota de voz como opção: a Meta decide pelo formato do ficheiro (um áudio ogg/opus aparece como nota de voz);
cabeçalho de modelo em base64: use uma imagem por URL.
Pedidos destes falham com CAPABILITY_UNAVAILABLE, nunca em silêncio.
Custos
O plano (Essencial, Negócio ou Escala) cobre os números e a API, sem limite de mensagens da nossa parte.
As mensagens são cobradas pela Meta, por mensagem entregue, à sua conta WhatsApp Business. O preço depende da categoria (marketing, utilidade, autenticação) e do país do cliente.
As respostas dentro da janela de 24 horas são mensagens de serviço, gratuitas até ao limite mensal que a Meta define.
Sem método de pagamento na conta da Meta, a Meta deixa de enviar mensagens, incluindo as respostas dentro da janela.
Os preços em vigor estão na página de preços da Plataforma WhatsApp da Meta.
