Pular para o conteúdo

Visão geral

A API do AutoZap é uma camada REST sobre a WhatsApp Cloud API oficial da Meta. Você chama a nossa API com uma chave interna; nós chamamos a Meta com o token do número conectado — que nunca é exposto.

URL base
https://eduzapi.com.br/api/v1
Formato
JSON (UTF-8). Datas em ISO 8601 (UTC).
Especificação
OpenAPI 3 (YAML)
O formato dos objetos de mensagem segue a documentação oficial da Cloud API. Recursos que dependem de aprovação da Meta (modelos, limites de envio, verificação do negócio) continuam sujeitos às regras dela.

Autenticação

Crie uma chave em Painel → Chaves de API e envie-a no cabeçalho Authorization. A chave completa aparece uma única vez; guardamos apenas um hash.

Authorization: Bearer sk_live_SUA_CHAVE

Cada chave tem escopos. Uma chamada sem o escopo necessário retorna 403 insufficient_scope.

EscopoPermite
messages:sendEnviar mensagens e marcar como lidas
messages:readConsultar mensagens e status de entrega
channels:readListar números conectados
templates:readListar modelos de mensagem
templates:writeCriar e apagar modelos de mensagem
profile:writeEditar o perfil comercial do número
media:readBaixar mídias recebidas
media:writeEnviar arquivos de mídia
conversations:readLer conversas do chat (plano API + Chat)
usage:readConsultar uso da API

Modo de teste

Chaves sk_test_ passam por toda a validação, mas não chamam a Meta: a resposta traz "test_mode": true e um id wamid.TEST…. Use-as para montar fluxos sem gastar mensagens. Para enviar de verdade, use uma chave sk_live_.

Erros e limites

Todo erro segue o mesmo envelope. Informe o request_id ao suporte.

{
  "error": {
    "code": "meta_api_error",
    "message": "A Meta recusou a operação.",
    "request_id": "6f1c…",
    "meta_error": { "code": 131047, "message": "Re-engagement message", "fbtrace_id": "A…" }
  }
}
HTTPCódigoQuando
400validation_error, invalid_recipient…Corpo inválido. O campo com problema vem em field.
401missing_api_key, invalid_api_keyChave ausente, inválida, expirada ou revogada.
403insufficient_scope, plan_requiredEscopo ou plano insuficiente.
404channel_not_foundO número não existe nesta conta ou não está ativo.
409idempotency_conflict, channel_unavailableRequisição repetida em andamento, ou número sem credencial válida.
429rate_limited, meta_api_errorLimite desta API (por chave) ou limite da Meta.
502meta_api_errorA Meta recusou. Detalhes originais em meta_error.
504meta_timeoutA Meta não respondeu a tempo.

Limite padrão: 120 requisições por minuto por chave (configurável pelo operador da plataforma). Os limites de mensagens da Meta (por número e por qualidade) se aplicam à parte.

Idempotência

Envie Idempotency-Key (8–128 caracteres, único por mensagem) para repetir com segurança após um erro de rede ou timeout. Se a mesma chave chegar de novo, devolvemos a resposta original com Idempotent-Replayed: true — a mensagem não é reenviada.

Conta

GET/me

Retorna a organização, o plano e os dados da chave usada.

Números

GET/channels
GET/channels/{phone_number_id}

Lista os números ativos. Use o phone_number_id nos demais endpoints.

{ "data": [ { "phone_number_id": "1234567890", "display_phone_number": "+55 11 4000-0000",
  "verified_name": "Minha Empresa", "quality_rating": "GREEN", "status": "active" } ] }

Enviar mensagem

POST/whatsapp/{phone_number_id}/messages

Escopo messages:send. Responde 201 quando a Meta aceita a mensagem; o status final (entregue, lida, falhou) chega por webhook.

curl -X POST https://eduzapi.com.br/api/v1/whatsapp/PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer sk_test_SUBSTITUA_PELA_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1234-aviso" \
  -d '{"to": "5511999998888", "type": "text", "text": {"body": "Mensagem de teste"}}'
# Python
import requests
r = requests.post(
    "https://eduzapi.com.br/api/v1/whatsapp/PHONE_NUMBER_ID/messages",
    headers={"Authorization": "Bearer sk_live_SUA_CHAVE"},
    json={"to": "5511999998888", "type": "text", "text": {"body": "Olá!"}},
    timeout=30,
)
print(r.status_code, r.json())
// Node.js (18+)
const r = await fetch("https://eduzapi.com.br/api/v1/whatsapp/PHONE_NUMBER_ID/messages", {
  method: "POST",
  headers: { Authorization: "Bearer sk_live_SUA_CHAVE", "Content-Type": "application/json" },
  body: JSON.stringify({ to: "5511999998888", type: "text", text: { body: "Olá!" } }),
});
console.log(r.status, await r.json());

Resposta:

{
  "data": {
    "id": "0b0d…",               // id interno da mensagem
    "wamid": "wamid.HBgM…",      // id da Meta (aparece nos webhooks de status)
    "status": "accepted",
    "type": "text",
    "contact_wa_id": "5511999998888",
    "meta_response": { "messaging_product": "whatsapp", "contacts": [...], "messages": [...] }
  },
  "test_mode": false,
  "request_id": "…"
}
Janela de 24 horas. Mensagens livres (texto, mídia…) só podem ser enviadas até 24h após a última mensagem do cliente. Fora disso, use um modelo aprovado (type: "template"). Respeite o consentimento (opt-in) do destinatário e as políticas do WhatsApp Business.

Tipos de mensagem

O corpo segue o objeto de mensagem da Cloud API — sem messaging_product, que nós adicionamos. Exemplos:

// Modelo aprovado com variáveis
{ "to": "5511999998888", "type": "template",
  "template": { "name": "pedido_enviado", "language": { "code": "pt_BR" },
    "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Ana" } ] } ] } }

// Imagem por link (ou por "id" de mídia enviada em /media)
{ "to": "5511999998888", "type": "image",
  "image": { "link": "https://exemplo.com/foto.jpg", "caption": "Seu produto" } }

// Documento
{ "to": "5511999998888", "type": "document",
  "document": { "link": "https://exemplo.com/boleto.pdf", "filename": "boleto.pdf" } }

// Botões de resposta
{ "to": "5511999998888", "type": "interactive",
  "interactive": { "type": "button", "body": { "text": "Confirma o horário?" },
    "action": { "buttons": [
      { "type": "reply", "reply": { "id": "sim", "title": "Sim" } },
      { "type": "reply", "reply": { "id": "nao", "title": "Não" } } ] } } }

// Localização
{ "to": "5511999998888", "type": "location",
  "location": { "latitude": -23.561, "longitude": -46.656, "name": "Loja Paulista" } }

// Reação a uma mensagem recebida
{ "to": "5511999998888", "type": "reaction",
  "reaction": { "message_id": "wamid.HBgM…", "emoji": "👍" } }

// Responder citando uma mensagem
{ "to": "5511999998888", "type": "text", "text": { "body": "Claro!" },
  "context": { "message_id": "wamid.HBgM…" } }

Tipos aceitos: text, template, image, video, audio, document, sticker, location, interactive, reaction, contacts.

Marcar como lida

POST/whatsapp/{phone_number_id}/messages/read
{ "message_id": "wamid.HBgM…" }

Consultar mensagens

GET/messages?phone_number_id=&direction=inbound|outbound&contact=&limit=50&before=
GET/messages/{id ou wamid}

Escopo messages:read. Retorna status de entrega e erros. O campo content só é preenchido no plano API + Chat (no plano API o conteúdo não é armazenado; você o recebe no webhook). Para paginar, envie before = next_before da página anterior.

Modelos

GET/whatsapp/{phone_number_id}/templates?status=APPROVED

Lista os modelos da conta do WhatsApp (WABA) diretamente na Meta. Modelos são criados no painel (página do número) ou no WhatsApp Manager e passam por análise da Meta.

Mídia

POST/whatsapp/{phone_number_id}/media

Upload multipart/form-data com o arquivo no campo file. Retorna {"data": {"id": "…"}} para usar em image.id, document.id etc.

curl -X POST https://eduzapi.com.br/api/v1/whatsapp/PHONE_NUMBER_ID/media \
  -H "Authorization: Bearer sk_live_SUA_CHAVE" -F "file=@foto.jpg;type=image/jpeg"
GET/whatsapp/{phone_number_id}/media/{media_id}

Baixa (stream) uma mídia recebida — o media_id chega no webhook message.received. A Meta mantém mídias recebidas por tempo limitado; baixe e guarde no seu sistema se precisar.

Conversas (plano API + Chat)

GET/conversations?status=open|resolved
GET/conversations/{id}/messages

Escopo conversations:read. Inclui window_open (janela de 24h aberta).

Uso

GET/usage?days=30

Contagens reais de requisições e mensagens no período. A cobrança das conversas/mensagens é feita pela Meta diretamente na conta do WhatsApp do negócio.

Webhooks — eventos

Cadastre uma URL HTTPS em Painel → Webhooks e escolha os eventos. Enviamos um POST JSON; responda com qualquer 2xx em até 10 s. Falhas são repetidas com espera crescente (2, 4, 8… minutos) até 6 tentativas.

EventoDescrição
message.receivedMensagem recebida de um contato
message.status.updatedStatus de mensagem enviada (enviada, entregue, lida, falhou)
message.sentMensagem enviada pela API ou pelo painel
channel.connectedNúmero conectado
channel.disconnectedNúmero desconectado
template.status.updatedModelo de mensagem aprovado/rejeitado pela Meta
api.request.failedRequisição à API recusada pela Meta
POST https://seu-sistema.com/webhook
X-Webhook-Id: 9b2e…            (id da entrega — use para deduplicar)
X-Webhook-Event: message.received
X-Webhook-Timestamp: 1767225600
X-Webhook-Signature: sha256=5d1c…

{
  "id": "9b2e…",
  "event": "message.received",
  "created_at": "2026-10-10T13:00:00+00:00",
  "data": {
    "phone_number_id": "1234567890",
    "contact": { "wa_id": "5511999998888", "name": "Ana" },
    "message": { "id": "wamid.HBgM…", "from": "5511999998888", "timestamp": "1767225600",
                 "type": "text", "text": { "body": "Olá!" } }
  }
}

Validar a assinatura

Calcule HMAC-SHA256(segredo, timestamp + "." + corpo_bruto) e compare com o cabeçalho em tempo constante. Rejeite timestamps com mais de 5 minutos para evitar replay. O segredo whsec_… aparece uma vez ao criar o webhook.

# Python (Flask)
import hashlib, hmac, time
from flask import request, abort

SECRET = "whsec_SEU_SEGREDO"

def verificar():
    ts = request.headers.get("X-Webhook-Timestamp", "")
    assinatura = request.headers.get("X-Webhook-Signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        abort(400)
    esperado = "sha256=" + hmac.new(SECRET.encode(), ts.encode() + b"." + request.get_data(),
                                    hashlib.sha256).hexdigest()
    if not hmac.compare_digest(esperado, assinatura):
        abort(401)
// Node.js (Express com express.raw({ type: "application/json" }))
const crypto = require("crypto");
function verificar(req) {
  const ts = req.get("X-Webhook-Timestamp");
  const esperado = "sha256=" + crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
    .update(ts + "." + req.body).digest("hex");
  const recebido = req.get("X-Webhook-Signature") || "";
  return Math.abs(Date.now() / 1000 - Number(ts)) < 300 &&
    recebido.length === esperado.length &&
    crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(recebido));
}

Exemplo com n8n

  1. Crie um nó Webhook (método POST) e copie a URL de produção.
  2. No painel, cadastre essa URL em Webhooks com o evento message.received.
  3. Processe {{ $json.body.data.message.text.body }} (texto recebido) e o remetente em data.contact.wa_id.
  4. Para responder, use um nó HTTP Request: POST para https://eduzapi.com.br/api/v1/whatsapp/PHONE_NUMBER_ID/messages, cabeçalho Authorization: Bearer sk_live_… e corpo JSON como nos exemplos acima.