ZAP-API
PreçosCasos de UsoBlogDocsLogin
Começar grátis
v1 · REST API · Estável

Documentação
ZAP-API

API REST para enviar e receber mensagens WhatsApp. Autentique com Bearer token, gerencie instâncias e configure webhooks em minutos.

Base URLhttps://api.zap-api.tech/v1
Abrir Dashboard

Referência completa da API — 250+ endpoints

Esta página é o início rápido. A referência interativa traz todos os endpoints — 18 tipos de mensagem, grupos, contatos, chats, perfil, status, catálogo business, comunidades, newsletters, chamadas e fila. É gerada do código, então nunca fica desatualizada.

Início RápidoAutenticaçãoInstânciasMensagensWebhooksRate LimitsErros

Referência

Início RápidoAutenticaçãoInstânciasMensagensWebhooksRate LimitsErros

Recursos

Tutoriais no BlogGerenciar Instâncias

Início Rápido

Em 3 passos você já está enviando mensagens WhatsApp via API.

1

Criar uma instância

No dashboard, acesse Instâncias → Nova Instância e dê um nome.

2

Conectar via QR Code

Clique em Conectar e escaneie o QR Code com seu WhatsApp.

3

Copiar o token

Acesse Credenciais na instância e copie o Bearer token.

curl
curl -X POST https://api.zap-api.tech/v1/instances/inst_xxx/send \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"phone":"5511999999999","type":"text","body":"Olá! API funcionando ✅"}'

Autenticação

Cada instância possui um Bearer token único. Inclua-o em todas as requisições no header Authorization.

Para obter o token, acesse Dashboard → Instâncias → Credenciais.

Header obrigatório

http
Authorization: Bearer tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Node.js (axios)

js
const axios = require('axios');

const api = axios.create({
  baseURL: 'https://api.zap-api.tech/v1',
  headers: {
    'Authorization': 'Bearer tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
});

const response = await api.post('/instances/inst_xxx/send', {
  phone: '5511999999999',
  type: 'text',
  body: 'Olá! Mensagem via ZAP API.',
});
console.log(response.data);

Python (requests)

python
import requests

headers = {
    "Authorization": "Bearer tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "Content-Type": "application/json",
}

resp = requests.post(
    "https://api.zap-api.tech/v1/instances/inst_xxx/send",
    json={"phone": "5511999999999", "type": "text", "body": "Olá via Python!"},
    headers=headers,
)
print(resp.json())

Instâncias

Cada instância representa uma conexão WhatsApp independente com seu próprio token.

GET/instances/:idDetalhes da instância
GET/instances/:id/statusStatus de conexão
GET/instances/:id/qrcodeQR Code para conectar
POST/instances/:id/connectConectar / reconectar
POST/instances/:id/pairingCódigo de pareamento
POST/instances/:id/disconnectDesconectar
Criar e excluir instâncias é feito pelo Dashboard → Instâncias (autenticação da conta). Os endpoints acima usam o Bearer token e operam sobre uma instância já existente.

GET /instances — listar

curl
curl https://api.zap-api.tech/v1/instances \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx"
json
{
  "instances": [
    {
      "id": "inst_abc123",
      "name": "Suporte",
      "status": "connected",
      "phone": "+5511999999999",
      "plan": "pro",
      "createdAt": "2026-01-15T10:00:00Z"
    }
  ]
}

POST /instances/:id/connect — conectar

Inicia (ou reinicia) a conexão da instância. Em seguida, busque o QR Code em /qrcode e escaneie pelo WhatsApp.

curl
curl -X POST https://api.zap-api.tech/v1/instances/inst_abc123/connect \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx"

GET /instances/:id/qrcode — QR Code

O QR Code expira em segundos. Se o status não for QRCODE, chame POST /connect primeiro e tente de novo.
curl
curl https://api.zap-api.tech/v1/instances/inst_abc123/qrcode \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx"
json
{
  "instanceId": "inst_abc123",
  "qrCode": "data:image/png;base64,iVBORw0KGgo...",
  "status": "qr_required"
}

GET /instances/:id/status — status de conexão

json
{
  "instanceId": "inst_abc123",
  "waStatus": "CONNECTED",
  "connected": true,
  "number": "5511999999999",
  "name": "Suporte",
  "profilePicUrl": null,
  "qrcode": null
}

Principais valores de waStatus:

CONNECTEDDISCONNECTEDQRCODE

Mensagens

POST/instances/:id/sendEnviar (18 tipos via campo type)
POST/instances/:id/send/batchEnvio em lote (até 100)
POST/instances/:id/send/scheduleEnvio agendado
Existe um único endpoint de envio. O tipo da mensagem é definido pelo campo type: text, image, audio, video, document, sticker, gif, location, contact, link, reaction, poll, buttons, list, product, order, pix, event.
Botões e listas chegam como texto numerado. buttons e list são aceitos e entregues — mas o WhatsApp só exibe botão clicável em conta oficial. Em conta conectada por QR Code ele descartaria a mensagem interativa, e ela sumiria. Por isso convertemos em texto com as opções numeradas antes de enviar: garante a entrega. A resposta vem com messageId normalmente, então não dá para perceber pelo retorno.
Para interação clicável de verdade, use poll (enquete) — é nativa. Atenção: o voto da enquete não é enviado ao seu webhook. Se o seu fluxo precisa ler a escolha por código, continue no texto numerado e leia a resposta do cliente pelo evento message.received.

POST /instances/:id/send — texto

CampoTipoObrigatórioDescrição
phonestringsimNúmero com DDI, só dígitos, sem + (ex: 5511999999999) ou JID de grupo (@g.us)
typestringsimTipo da mensagem — use "text"
bodystringsimConteúdo do texto (1–4096 caracteres)
previewUrlbooleanopcionalGera preview do link no texto
quotedMessageIdstringopcionalID de uma mensagem para responder/citar
curl
curl -X POST https://api.zap-api.tech/v1/instances/inst_abc123/send \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "5511999999999",
    "type": "text",
    "body": "Seu pedido #1234 foi confirmado. ✅"
  }'
O campo do texto chama-se body — nunca text ou message. Um 200 significa que a mensagem foi aceita na fila — acompanhe a entrega real em /messages/status/:msgId ou pelo webhook message.status.

POST /instances/:id/send — mídia (imagem, vídeo, documento…)

CampoTipoObrigatórioDescrição
phonestringsimNúmero com DDI, só dígitos, sem +
typestringsimimage, video, gif, audio, document, sticker…
mediaUrlstringsimURL pública do arquivo (jpg, png, pdf, mp4…)
captionstringopcionalLegenda (image / video / gif / document)
fileNamestringopcionalNome do arquivo (obrigatório para document)
curl
curl -X POST https://api.zap-api.tech/v1/instances/inst_abc123/send \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "5511999999999",
    "type": "document",
    "mediaUrl": "https://exemplo.com/boleto.pdf",
    "fileName": "boleto-fev-2026.pdf",
    "caption": "Seu boleto de fevereiro"
  }'

POST /instances/:id/send — enquete (poll)

Interação clicável nativa: diferente de buttons e list, a enquete não é convertida em texto — ela chega como enquete de verdade.

CampoTipoObrigatórioDescrição
phonestringsimNúmero com DDI, só dígitos, sem + (ex: 5511999999999) ou JID de grupo (@g.us)
typestringsimTipo da mensagem — use "poll"
pollQuestionstringsimPergunta da enquete (1–255 caracteres)
pollOptionsstring[]simOpções: mínimo 2, máximo 12, cada uma de 1 a 100 caracteres
pollMultiSelectbooleanopcionalPermite marcar mais de uma opção (padrão: false)
quotedMessageIdstringopcionalID de uma mensagem para responder/citar
curl
curl -X POST https://api.zap-api.tech/v1/instances/inst_abc123/send \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "5511999999999",
    "type": "poll",
    "pollQuestion": "Qual horário fica melhor para a entrega?",
    "pollOptions": ["Manhã (8h–12h)", "Tarde (12h–18h)", "Não estarei em casa"],
    "pollMultiSelect": false
  }'
O voto da enquete não é enviado ao seu webhook — não existe evento de resposta de enquete. Use a enquete para perguntar de forma clicável; se o seu fluxo precisa ler a escolha por código, peça a resposta em texto e leia pelo evento message.received.

Webhooks

Configure uma URL no seu servidor para receber eventos em tempo real: mensagens recebidas, mudanças de status e confirmações de entrega.

Configurar webhook

Configure via API com PUT /instances/:id/webhook ou pelo Dashboard em Instâncias → Webhook. A URL deve ser HTTPS e pública (não localhost).

curl
curl -X PUT https://api.zap-api.tech/v1/instances/inst_abc123/webhook \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seu-servidor.com/webhook",
    "events": ["message.received", "message.status"],
    "secret": "sua_chave_secreta"
  }'
Seu servidor deve responder com HTTP 200 em menos de 5 segundos. Falhas consecutivas desativam o webhook temporariamente — teste o disparo com POST /webhook/test.

Eventos disponíveis

message.receivedNova mensagem recebida
message.sentMensagem enviada pela instância
message.statusMudança de status (entregue / lida)
message.readLida pelo destinatário (✓✓ azul)
instance.connectedInstância conectada com sucesso
instance.disconnectedInstância desconectada

São 21 eventos no total (também grupos, contatos, presença, chamadas, status e newsletter). Inscreva só os que precisar no campo events.

Payload — message.received

json
{
  "event": "message.received",
  "instanceId": "inst_abc123",
  "timestamp": "2026-02-15T14:30:00Z",
  "data": {
    "messageId": "msg_xyz789",
    "phone": "5511999999999",
    "name": "João Silva",
    "type": "text",
    "body": "Olá, quero saber sobre o produto",
    "fromMe": false,
    "timestamp": 1771166400
  }
}

Dependendo do tipo da mensagem, o data traz também: mediaUrl, mimeType, fileName e caption (mídia), latitude e longitude (localização), buttonReply (clique em botão/lista) e lid (contatos com identificador oculto). Campos ausentes simplesmente não vêm no JSON — trate sempre com valor padrão.

Localização e cliques em botão

Quando o contato compartilha localização, as coordenadas vêm no próprio evento. E quando ele clica num botão ou item de lista, você recebe o texto em body e o identificador da opção em buttonReply — útil para roteirizar o funil sem depender do texto digitado.

json
{
  "event": "message.received",
  "data": {
    "type": "location",
    "latitude": -23.55052,
    "longitude": -46.633308
  }
}

{
  "event": "message.received",
  "data": {
    "type": "text",
    "body": "Quero falar com vendas",
    "buttonReply": { "id": "opt_vendas", "text": "Quero falar com vendas" }
  }
}

Mídia recebida — áudio, imagem, vídeo e documento

Quando o contato envia mídia, o payload traz mediaUrl — um link temporário e assinado para baixar o arquivo — além do mimeType e, em documentos, do fileName. A legenda, quando existir, vem em caption.

json
{
  "event": "message.received",
  "data": {
    "messageId": "msg_xyz789",
    "phone": "5511999999999",
    "type": "audio",
    "mediaUrl": "https://.../zap-inbound-media/...?X-Amz-Signature=...",
    "mimeType": "audio/ogg; codecs=opus"
  }
}
O mediaUrl é temporário (vale 7 dias). Prefira baixar o arquivo assim que receber o evento. Se o link já tiver expirado, use o endpoint abaixo para gerar um novo — não é preciso pedir a mensagem de novo ao contato.

GET /instances/:id/messages/:msgId/media — gerar novo link

O link do webhook expira em 7 dias. Se você processa os eventos de forma assíncrona (fila, reprocessamento, agente de IA que roda depois), use este endpoint com o messageId que veio no payload para gerar um link novo enquanto o arquivo estiver dentro do período de retenção.

curl
curl https://api.zap-api.tech/v1/instances/inst_abc123/messages/msg_xyz789/media \
  -H "Authorization: Bearer tk_xxxxxxxxxxxx"
json
{
  "messageId": "msg_xyz789",
  "type": "audio",
  "mediaUrl": "https://.../zap-inbound-media/...?X-Amz-Signature=...",
  "mimeType": "audio/ogg; codecs=opus",
  "bytes": 14238,
  "expiresIn": 604800,
  "expiresAt": "2026-08-25T14:30:00.000Z"
}
200Link novo gerado — use mediaUrl até expiresAt
404NO_MEDIAA mensagem existe, mas não tem mídia (texto, ou recebida antes de 18/08/2026)
410MEDIA_GONEO arquivo passou do período de retenção — não há como recuperar
503MEDIA_STORAGE_UNAVAILABLEIndisponibilidade nossa — tente novamente mais tarde
O endpoint é escopado por instância: use o mesmo instanceId que recebeu a mensagem. Chamar /v1/messages/:msgId/media (sem a instância) devolve 404 com o caminho correto.

Contatos com LID — envie sempre para o phone

Alguns contatos chegam com um lid (Linked ID) — o WhatsApp oculta o número real por privacidade. Nesses casos o payload traz os dois campos: o número real em phone e o identificador em lid.

json
{
  "event": "message.received",
  "data": {
    "phone": "5535997022148",       // número real — USE este para enviar
    "lid": "143435449282736@lid",   // identificador — NÃO use para enviar
    "name": "Contato",
    "type": "text",
    "body": "..."
  }
}
Para enviar, use sempre o phone — nunca o lid. Enviar para um …@lid abre um “chat fantasma” (o WhatsApp trata os dígitos do LID como um número solto, às vezes até estrangeiro) e a mensagem não chega — mesmo a API respondendo 200 / sent, porque o WhatsApp aceitou o envio. Pode mandar o phone exatamente como veio no webhook: o gateway completa o 9º dígito do número brasileiro automaticamente.

Verificação HMAC-SHA256

Configure um Webhook Secret na instância. Cada entrega traz o header X-ZapAPI-Signature-256 no formato sha256=<hmac-hex> (padrão GitHub). O header legado X-Zap-Signature é mantido por compatibilidade.

js
const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const payload = JSON.stringify(req.body);
  const header = req.headers['x-zapapi-signature-256'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  // comparação em tempo constante (evita timing attack)
  const ok = header.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
  if (!ok) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const { event, data } = req.body;
  console.log('Evento recebido:', event, data);
  res.json({ ok: true });
});

Rate Limits

Limite de envio por instância, por minuto. Ao atingir o teto a API retorna HTTP 429 com o header Retry-After. Não há limite mensal de envio — a cobrança é apenas por instância ativa.

PlanoEnvio/minPreço por instânciaRetenção de logs
Trial30Grátis por 7 dias—
Pago120R$ 49 (1ª e 2ª) · R$ 29 (3ª+)30 dias

Erros

Todos os erros seguem o formato: { "error": "ERROR_CODE", "message": "texto explicativo" }. Em um 404 de rota não-escopada, a resposta inclui o campo hint com o caminho correto.

HTTPSignificadoCausa comum
200OKAceito (no envio: enfileirado, não necessariamente entregue)
400VALIDATION_ERRORCampo obrigatório ausente/inválido (ex: phone, type, body)
401UnauthorizedBearer token inválido ou ausente
404ROUTE_NOT_SCOPEDRota chamada sem /instances/:id — use o campo hint da resposta
409NOT_CONNECTEDInstância não está conectada (waStatus ≠ CONNECTED)
422SELF_SEND_BLOCKEDTentativa de enviar para o próprio número da instância
429Too Many RequestsRate limit por minuto atingido — veja Retry-After
503CIRCUIT_OPENCircuit breaker aberto após falhas — aguarde ~60s

Pronto para integrar?

7 dias grátis · sem cartão · instância funcionando em minutos.

Criar instânciaSuporte
ZAP-API

API REST para WhatsApp com webhooks assinados, Meta Pixel/CAPI e compliance LGPD. Sem aprovação da Meta.

Status operacional🇧🇷 Feito no Brasil

Produto

  • Preços
  • Casos de uso
  • Automação WhatsApp
  • API não oficial
  • Comparativo
  • API WhatsApp grátis
  • Dashboard

Recursos

  • Documentação
  • Blog
  • Glossário
  • RSS Feed

Empresa

  • Sobre
  • Imprensa
  • Termos de uso
  • Privacidade
  • Criar conta
  • Login

Contato

  • [email protected]
  • [email protected]
  • Resposta em até 24h úteis
© 2026 ZAP-API — Todos os direitos reservados·CNPJ 42.130.949/0001-56·Termos·Privacidade

Desenvolvido por PreviusIA