Documentação da API
Envie mensagens pelo número da sua empresa na API oficial do WhatsApp (Meta Cloud API) e receba respostas e status de entrega no seu sistema.
Antes de começar
Tudo o que vem antes da integração é feito no portal, uma vez só:
- Conectar o número da empresa pelo Facebook (painel).
- Criar os modelos de mensagem e esperar a aprovação da Meta (Modelos). Também dá para criar pela API.
- Gerar a chave de API (API e webhook). Ela aparece uma única vez: guarde num cofre de senhas.
- Cadastrar o webhook para receber respostas e status (API e webhook).
A Meta cobra cada mensagem de modelo enviada, no cartão cadastrado na conta do WhatsApp da sua empresa. Texto livre dentro das 24 h da última mensagem do cliente não é cobrado.
Autenticação e limites
Toda chamada leva a chave no cabeçalho Authorization:
Authorization: Bearer at_xxxxxxxxxxxxxxxxxxxxxxxx
A chave dá acesso só à sua conta: número, modelos e envios dela. Gerar uma chave nova no portal invalida a anterior na hora. Nunca coloque a chave em código que roda no navegador ou no celular do cliente.
| Limite | Valor | Ao passar |
|---|---|---|
| por chave | 600 chamadas por minuto | 429 RATE_LIMITED com Retry-After em segundos |
| chave inválida | 20 tentativas por endereço IP em 10 min | 429 RATE_LIMITED para esse IP |
Sucesso vem com "success": true. Falha vem com "success": false, um error_code estável (use este no seu código) e um error legível (mostre para a pessoa, não compare).
{ "success": false, "error_code": "TEMPLATE_REQUIRED", "error": "Este cliente não escreveu nas últimas 24h: ..." }Enviar mensagem POST/messages
A regra da Meta decide o que pode ser enviado:
- Modelo aprovado (template): a qualquer momento. É o único jeito de falar com quem não escreveu para a empresa nas últimas 24 h.
- Texto livre (text): só até 24 h depois da última mensagem do cliente. Fora disso a chamada é recusada com TEMPLATE_REQUIRED.
curl -X POST https://axionventures.com.br/talk/api/v1/messages \
-H "Authorization: Bearer $AXION_TALK_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-1234-confirmacao" \
-d '{
"to": "5562981234567",
"category": "transactional",
"template": {
"name": "confirmacao_pedido",
"language": "pt_BR",
"components": [
{ "type": "body", "parameters": [
{ "type": "text", "text": "Maria" },
{ "type": "text", "text": "#1234" }
] }
]
}
}'
| Campo | O que é |
|---|---|
| to | Número de destino com DDI e DDD, só dígitos (ex.: 5562981234567). |
| template | Modelo aprovado: name, language e components no formato da Meta, com um parâmetro para cada variável {{n}}, na ordem. |
| text | Texto livre (só dentro das 24 h). Use template ou text. |
| category | transactional: aviso que o cliente provocou (pedido, agendamento, pagamento). marketing (padrão): promoção, campanha. Para um mesmo contato, transactional aceita até 12 mensagens em 24 h. |
Resposta 202: a mensagem entrou na fila. A entrega é confirmada depois, pelo webhook ou pela consulta.
{ "success": true, "id": "4f0c9a8e-5b1d-4c7e-9f61-0a2b3c4d5e6f", "status": "queued" }
Sem envio em dobro
Mande o cabeçalho Idempotency-Key com um valor único por aviso (ex.: o id do pedido + o tipo do aviso). Repetir a chamada com a mesma chave devolve a mensagem original, sem enviar de novo. Mesmo sem o cabeçalho, o mesmo conteúdo para o mesmo número em 24 h não sai duas vezes. Nos dois casos a resposta traz "deduped": true e o id da original.
Se a chamada estourar o tempo ou cair a conexão, repita com a mesma Idempotency-Key: nunca gera duplicata.
Quem pediu para parar
Quando o cliente responde com PARAR, SAIR, CANCELAR ou STOP, novas mensagens para ele são recusadas com OPTED_OUT.
Consultar mensagem GET/messages/{id}
curl https://axionventures.com.br/talk/api/v1/messages/4f0c9a8e-5b1d-4c7e-9f61-0a2b3c4d5e6f \
-H "Authorization: Bearer $AXION_TALK_KEY"
{
"success": true,
"message": {
"id": "4f0c9a8e-5b1d-4c7e-9f61-0a2b3c4d5e6f",
"to": "5562981234567",
"type": "template",
"template": "confirmacao_pedido",
"status": "delivered",
"category": "transactional",
"error_code": null,
"retry_safe": false,
"provider_message_id": "wamid.HBgM...",
"created_at": "2026-10-27T13:02:11.204Z",
"updated_at": "2026-10-27T13:02:14.871Z"
}
}
| status | Significa | O que fazer |
|---|---|---|
| queued | Na fila, ainda não foi para a Meta. | Esperar. |
| sending | Sendo entregue à Meta agora. | Esperar. |
| sent | A Meta aceitou. | Esperar a entrega. |
| delivered | Chegou no WhatsApp do cliente. | Nada. |
| failed | Com certeza não chegou. O motivo vem em error_code. | Corrigir a causa e mandar de novo, se fizer sentido (retry_safe: true). |
| uncertain | A Meta não confirmou se recebeu (queda no meio do envio). Pode ter chegado. | Não reenvie automaticamente: um status tardio da Meta ainda corrige para sent ou failed. |
Motivos comuns de failed: NUMBER_NOT_ON_WHATSAPP (número sem WhatsApp), TEMPLATE_REQUIRED (passou das 24 h), TEMPLATE_INVALID (modelo não aprovado ou parâmetros que não batem), META_* (recusa da Meta com o código dela, por exemplo conta sem forma de pagamento).
Modelos
GET/templates lista os modelos da conta com o status na Meta (APPROVED, PENDING, REJECTED…), no formato da Meta.
POST/templates cria e manda para a análise da Meta (minutos a horas). O resultado chega no webhook template.status.
curl -X POST https://axionventures.com.br/talk/api/v1/templates \
-H "Authorization: Bearer $AXION_TALK_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "confirmacao_pedido",
"language": "pt_BR",
"category": "UTILITY",
"components": [
{ "type": "BODY",
"text": "Olá {{1}}, recebemos seu pedido {{2}}. Avisamos quando sair para entrega.",
"example": { "body_text": [["Maria", "#1234"]] } }
]
}'
name: minúsculas, números e _. category: UTILITY (aviso pedido pelo cliente), MARKETING (promoção) ou AUTHENTICATION (código de acesso); a Meta pode reclassificar. Cada variável precisa de exemplo. Recusa imediata da Meta (nome repetido, formato) volta 422 com a mensagem dela em error.
DELETE/templates/{name} apaga o modelo na Meta (todos os idiomas com esse nome).
Número GET/number
O número conectado e o estado dele. Sem número conectado, number vem null.
{
"success": true,
"number": {
"phone_number": "5562981234567",
"status": "connected",
"waba_id": "1234567890",
"phone_number_id": "1098765432",
"connected_at": "2026-10-20T18:44:02.117Z"
}
}
status diferente de connected (por exemplo, a permissão foi removida no Facebook): as mensagens esperam na fila; reconecte no portal.
Webhook
Cadastre no portal uma URL HTTPS pública. Cada evento chega num POST com este envelope:
{
"event": "message.status",
"payload": { ... },
"sent_at": "2026-10-27T13:02:14.990Z"
}
| Cabeçalho | Conteúdo |
|---|---|
| X-Whats360-Signature | HMAC-SHA256, em hexadecimal, do corpo cru com o segredo mostrado ao salvar o webhook. |
| X-Whats360-Event | O mesmo valor de event. |
Confira a assinatura antes de confiar no conteúdo, e responda 2xx em até 10 segundos. Sem 2xx, tentamos de novo: são até 5 tentativas em cerca de 6 minutos, depois o evento é descartado; a consulta da mensagem continua valendo. O mesmo evento pode chegar mais de uma vez: trate pelo message_id.
const crypto = require('crypto');
// Use o corpo CRU (bytes), antes de qualquer JSON.parse.
function valido(corpoCru, assinatura, segredo) {
const esperado = crypto.createHmac('sha256', segredo).update(corpoCru).digest('hex');
const a = Buffer.from(String(assinatura || ''));
const b = Buffer.from(esperado);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/webhooks/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
if (!valido(req.body, req.get('X-Whats360-Signature'), process.env.AXION_TALK_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const { event, payload } = JSON.parse(req.body);
res.sendStatus(200); // responda logo; processe depois
processar(event, payload);
});
| event | Quando | payload |
|---|---|---|
| message.inbound | Um cliente escreveu para a empresa. Abre a janela de 24 h para texto livre. | phone, text, message_id, type (text, image, button…), profile_name, raw (mensagem como a Meta mandou) |
| message.status | Uma mensagem sua mudou para sent, delivered, failed ou uncertain. | message_id (o id do envio), to, status, error_code, retry_safe, provider_message_id |
| template.status | A Meta aprovou, recusou, pausou ou desativou um modelo. | name, language, status (APPROVED, REJECTED…), reason |
| session.connected | O número foi conectado (ou reconectado) à API oficial. | phone_number, provider |
| system.alert | Aviso sobre o seu número (por exemplo, desconectado há mais de 10 min). Volta com state: "resolved" quando normaliza. | key, severity, state, message |
| webhook.test | Botão "Enviar evento de teste" do portal. | message |
Erros
| error_code | HTTP | Causa |
|---|---|---|
| UNAUTHORIZED | 401 | Chave ausente, errada ou substituída por uma nova. |
| RATE_LIMITED | 429 | Limite de chamadas. Espere o Retry-After. |
| INVALID | 422 | Faltou to ou o conteúdo (template ou text). |
| INVALID_TEMPLATE | 422 | Modelo mal formado (nome, idioma, componentes) ou recusado pela Meta ao criar. |
| INVALID_CATEGORY | 422 | category diferente de marketing e transactional. |
| TEMPLATE_REQUIRED | 422 | Texto livre para quem não escreveu nas últimas 24 h. Use um modelo. |
| OPTED_OUT | 422 | O cliente pediu para não receber mais mensagens. |
| CONTACT_DAILY_LIMIT | 422 | Teto de mensagens transactional para o mesmo contato em 24 h. retry_after diz quando libera. |
| NO_NUMBER | 409 | Nenhum número conectado na conta. |
| NOT_CONNECTED | 409 | O número está desconectado. Reconecte no portal. |
| NOT_FOUND | 404 | Mensagem de outra conta, id inexistente ou rota errada. |
| INVALID_JSON | 400 | O corpo não é JSON válido. |
| LEADER_UNAVAILABLE | 503 | Serviço indisponível por instantes. Repita com a mesma Idempotency-Key. |
Dúvidas: talk@axionventures.com.br.