Axion Talk
Portal do cliente
Axion Talk · API oficial do WhatsApp

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.

https://axionventures.com.br/talk/api/v1
Endereço base de todas as chamadas. Corpo e respostas em JSON (UTF-8).

Antes de começar

Tudo o que vem antes da integração é feito no portal, uma vez só:

  1. Conectar o número da empresa pelo Facebook (painel).
  2. Criar os modelos de mensagem e esperar a aprovação da Meta (Modelos). Também dá para criar pela API.
  3. Gerar a chave de API (API e webhook). Ela aparece uma única vez: guarde num cofre de senhas.
  4. 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.

LimiteValorAo passar
por chave600 chamadas por minuto429 RATE_LIMITED com Retry-After em segundos
chave inválida20 tentativas por endereço IP em 10 min429 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" }
        ] }
      ]
    }
  }'
CampoO que é
toNúmero de destino com DDI e DDD, só dígitos (ex.: 5562981234567).
templateModelo aprovado: name, language e components no formato da Meta, com um parâmetro para cada variável {{n}}, na ordem.
textTexto livre (só dentro das 24 h). Use template ou text.
categorytransactional: 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"
  }
}
statusSignificaO que fazer
queuedNa fila, ainda não foi para a Meta.Esperar.
sendingSendo entregue à Meta agora.Esperar.
sentA Meta aceitou.Esperar a entrega.
deliveredChegou no WhatsApp do cliente.Nada.
failedCom certeza não chegou. O motivo vem em error_code.Corrigir a causa e mandar de novo, se fizer sentido (retry_safe: true).
uncertainA 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çalhoConteúdo
X-Whats360-SignatureHMAC-SHA256, em hexadecimal, do corpo cru com o segredo mostrado ao salvar o webhook.
X-Whats360-EventO 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);
});
eventQuandopayload
message.inboundUm 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.statusUma 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.statusA Meta aprovou, recusou, pausou ou desativou um modelo.name, language, status (APPROVED, REJECTED…), reason
session.connectedO número foi conectado (ou reconectado) à API oficial.phone_number, provider
system.alertAviso sobre o seu número (por exemplo, desconectado há mais de 10 min). Volta com state: "resolved" quando normaliza.key, severity, state, message
webhook.testBotão "Enviar evento de teste" do portal.message

Erros

error_codeHTTPCausa
UNAUTHORIZED401Chave ausente, errada ou substituída por uma nova.
RATE_LIMITED429Limite de chamadas. Espere o Retry-After.
INVALID422Faltou to ou o conteúdo (template ou text).
INVALID_TEMPLATE422Modelo mal formado (nome, idioma, componentes) ou recusado pela Meta ao criar.
INVALID_CATEGORY422category diferente de marketing e transactional.
TEMPLATE_REQUIRED422Texto livre para quem não escreveu nas últimas 24 h. Use um modelo.
OPTED_OUT422O cliente pediu para não receber mais mensagens.
CONTACT_DAILY_LIMIT422Teto de mensagens transactional para o mesmo contato em 24 h. retry_after diz quando libera.
NO_NUMBER409Nenhum número conectado na conta.
NOT_CONNECTED409O número está desconectado. Reconecte no portal.
NOT_FOUND404Mensagem de outra conta, id inexistente ou rota errada.
INVALID_JSON400O corpo não é JSON válido.
LEADER_UNAVAILABLE503Serviço indisponível por instantes. Repita com a mesma Idempotency-Key.

Dúvidas: talk@axionventures.com.br.