Documentação da API

Como integrar sua aplicação ao WhatsApp pelo Zapvio: enviar mensagens, receber respostas e acompanhar entregas.

Visão geral

Com duas informações, o ID e o token da instância, sua aplicação envia mensagens de WhatsApp por HTTP e recebe as respostas por webhook. Cada instância é um número de WhatsApp conectado ao Zapvio.

A API usa JSON em todas as requisições e respostas e não exige nenhuma biblioteca: qualquer linguagem que faça uma chamada HTTP serve.

  1. Crie sua conta no painel do Zapvio. Depois, crie uma instância e copie a URL da API, que já contém o ID e o token.
  2. Conecte o número lendo o QR Code (Conectar o número).
  3. Envie mensagens com POST /send-text e os demais endpoints de envio.
  4. Cadastre a URL do seu webhook para receber mensagens e confirmações de entrega e leitura.

URL base e autenticação

Toda chamada usa esta URL base, com o ID e o token da instância no próprio caminho:

{BASE}/instances/{ID}/token/{TOKEN}

Não há cabeçalho de autorização; basta enviar Content-Type: application/json nas chamadas com corpo. Nos exemplos abaixo, troque SEU_ID e SEU_TOKEN pelos da sua instância.

Onde pegar: crie sua conta no painel do Zapvio (Criar conta) e crie a instância. No card dela, menu ⋯ → Copiar URL da API, ou em Meus Tokens. Quantas instâncias você pode ter depende do seu plano.

Cuidados com o token

  • Quem tem a URL completa controla o número: guarde-a só no servidor da sua aplicação, nunca no código do navegador ou de um app.
  • Use sempre https://.
  • Se o token vazar, peça ao administrador para excluir a instância e criar outra; o token antigo deixa de funcionar na hora.

Conectar o número

Uma instância só envia depois de conectada a um WhatsApp pelo QR Code. A conexão fica salva: depois de lido o QR uma vez, o Zapvio reconecta sozinho, inclusive após reiniciar.

MétodoCaminhoRetorna
GET/status{ id, status, connected, phone, profileName }
GET/qr-code{ connected, status, value }; value = texto do QR, para você gerar a imagem
GET/qr-code/image{ connected, status, value }; value = imagem PNG pronta (data:image/png;base64,...)

O status pode ser disconnected, connecting, qr (aguardando leitura) ou connected.

Mostrar o QR dentro da sua aplicação

  1. Chame GET /qr-code/image. Na primeira chamada a instância começa a conectar, e o value pode vir null por alguns segundos.
  2. Repita a cada 3 segundos e mostre o value numa tag <img>. O QR muda a cada ~20 segundos, então exiba sempre o mais recente.
  3. O usuário abre o WhatsApp → Aparelhos conectados → Conectar aparelho e lê o código.
  4. Quando a resposta trouxer connected: true, pare de consultar. O webhook também recebe o evento ConnectedCallback.
curl {BASE}/instances/SEU_ID/token/SEU_TOKEN/status
{ "id": "SEU_ID", "status": "connected", "connected": true, "phone": "5543999999999", "profileName": "Loja X" }

Enviar mensagens

Todos os envios são POST com corpo JSON e devolvem { "messageId": "...", "phone": "..." } quando o WhatsApp aceita a mensagem. Guarde o messageId: é ele que chega depois nos webhooks de entrega e leitura.

CaminhoCorpoUso
/send-textphone, messageTexto
/send-filephone, file, fileName?, mimetype?, caption?Qualquer arquivo; imagem vira foto com legenda, o resto vai como documento
/send-imagephone, image, caption?Imagem com legenda
/send-documentphone, document, fileName?, mimetype?PDF, planilha etc.
/send-audiophone, audioÁudio, enviado como mensagem de voz

Campos com ? são opcionais. Arquivos (file, image, document, audio) aceitam uma URL pública (https://...) ou o conteúdo em base64, com ou sem o prefixo data:<tipo>;base64,. O limite do corpo é de 25 MB.

Texto, em cURL

curl -X POST {BASE}/instances/SEU_ID/token/SEU_TOKEN/send-text \
  -H "Content-Type: application/json" \
  -d '{"phone": "5543999999999", "message": "Olá! Seu pedido foi enviado."}'

Arquivo por URL, em JavaScript (Node 18+)

const BASE = process.env.ZAPVIO_URL; // {BASE}/instances/SEU_ID/token/SEU_TOKEN

const resposta = await fetch(`${BASE}/send-file`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    phone: "5543999999999",
    file: "https://exemplo.com/boleto.pdf",
    fileName: "boleto.pdf",
    caption: "Segue o seu boleto",
  }),
});
console.log(await resposta.json()); // { messageId: "3EB0...", phone: "554399999999" }

Texto, em Python

import os, requests

BASE = os.environ["ZAPVIO_URL"]
r = requests.post(f"{BASE}/send-text", json={"phone": "5543999999999", "message": "Olá!"}, timeout=60)
r.raise_for_status()
print(r.json())

Se a instância tiver atraso ou simular digitação ligados, a resposta só volta depois desse tempo (até 30 segundos). Use um timeout de pelo menos 60 segundos.

Telefone, nono dígito e grupos

Envie o phone com código do país e DDD, só dígitos: 5543999999999. Espaços, +, parênteses e traços são ignorados.

  • Sem o 55: números com 10 ou 11 dígitos (43999999999) são tratados como brasileiros e recebem o 55 automaticamente.
  • Nono dígito: antes de enviar, o Zapvio pergunta ao WhatsApp qual é o número real da conta. Muitas contas brasileiras existem sem o 9, e isso é resolvido sozinho; por isso o phone da resposta pode vir sem ele.
  • Número sem WhatsApp: o envio responde 404 com "o número ... não tem WhatsApp", sem mensagem fantasma.

Grupos: use o ID do grupo (JID, terminado em @g.us) no campo phone. A instância precisa participar do grupo.

MétodoCaminhoRetorna
GET/groups[{ id, name, participants }], os grupos de que o número participa
GET/phone-exists/{phone}{ exists, jid }, para validar um número antes de cadastrá-lo
curl -X POST {BASE}/instances/SEU_ID/token/SEU_TOKEN/send-text \
  -H "Content-Type: application/json" \
  -d '{"phone": "120363000000000000@g.us", "message": "Bom dia, equipe!"}'

Agendar mensagens

O Zapvio guarda a mensagem e envia na hora marcada, com precisão de cerca de 15 segundos. Se a instância estiver desconectada nessa hora, o envio acontece assim que ela reconectar.

MétodoCaminhoCorpo / retorno
POST/schedulephone, message?, file?, fileName?, mimetype?, at (obrigatório) → o agendamento, com id e status: "pending"
GET/scheduledLista dos agendamentos da instância
DELETE/scheduled/{id}Cancela um agendamento ainda pendente

Informe message, file ou os dois (o texto vira a legenda do arquivo). O at é uma data ISO 8601 com fuso, por exemplo 2026-10-28T09:00:00-03:00; datas no passado são recusadas.

O status vai de pending para sent (com o messageId), error (com o motivo em error) ou canceled.

curl -X POST {BASE}/instances/SEU_ID/token/SEU_TOKEN/schedule \
  -H "Content-Type: application/json" \
  -d '{"phone": "5543999999999", "message": "Lembrete: sua consulta é amanhã às 10h.", "at": "2026-10-28T09:00:00-03:00"}'

Webhooks: receber mensagens e status

O Zapvio faz um POST com JSON na URL que você cadastrar, a cada evento da instância. Sua rota deve responder 2xx em até 10 segundos; se falhar, o Zapvio tenta de novo mais 2 vezes (5 e 10 segundos depois).

Como cadastrar

  • Pelo painel: engrenagem da instância → Webhooks → Adicionar novo. Dá para ter várias URLs, cada uma recebendo só os eventos escolhidos.
  • Pela API (uma URL, todos os eventos): PUT /update-webhook com { "value": "https://sua-aplicacao.com/webhook" }. Enviar "value": null remove. Essa chamada substitui as URLs cadastradas pelo painel.
Evento (type)Quando chegaCampos principais
ReceivedCallbackMensagem recebida ou enviada pelo próprio celularmessageId, fromMe, phone, isGroup, participant, senderName, messageType, text, raw
MessageStatusCallbackMudança de status de uma mensagem enviadamessageId, phone, status (SENT, RECEIVED, READ, PLAYED)
ConnectedCallbackO número conectouconnected: true, phone
DisconnectedCallbackO número desconectouconnected: false, reason (logout ou logged_out)

Todo evento também traz type, instanceId e timestamp (milissegundos). Em grupos, phone é o ID do grupo e participant é quem enviou. O text traz o texto ou a legenda; para mídias e outros tipos, use messageType e o objeto raw.

Mensagem recebida

{
  "type": "ReceivedCallback",
  "instanceId": "SEU_ID",
  "messageId": "3EB0C431C26A1916E1F0",
  "fromMe": false,
  "phone": "554399999999",
  "isGroup": false,
  "participant": null,
  "senderName": "Maria",
  "messageType": "conversation",
  "text": "Oi, quero saber do meu pedido",
  "raw": { "conversation": "Oi, quero saber do meu pedido" },
  "timestamp": 1793130000000
}

Mensagem lida

{ "type": "MessageStatusCallback", "instanceId": "SEU_ID", "messageId": "3EB0CBFBD0F3C32B9FB647", "phone": "554399999999", "status": "READ", "timestamp": 1793130060000 }

Exemplo de rota (Node + Express)

app.post("/webhook", express.json({ limit: "10mb" }), (req, res) => {
  res.sendStatus(200); // responda logo; processe depois
  const ev = req.body;
  if (ev.type === "ReceivedCallback" && !ev.fromMe) {
    filaDeAtendimento.add({ de: ev.phone, nome: ev.senderName, texto: ev.text });
  }
  if (ev.type === "MessageStatusCallback") {
    atualizarStatus(ev.messageId, ev.status);
  }
});

O mesmo evento pode chegar mais de uma vez numa nova tentativa: use o messageId para ignorar repetidos.

Gerenciar a conexão

MétodoCaminhoO que faz
POST/restartFecha e reabre a conexão sem desparear o número. Use quando os envios travarem.
POST/disconnectDesconecta o aparelho e apaga a sessão. Para voltar, é preciso ler um novo QR Code.

Os dois respondem com o mesmo formato de /status. Use /disconnect só quando o cliente quiser trocar de número ou encerrar o serviço.

Erros

Todo erro responde em JSON no formato { "error": "descrição em português" }.

CódigoSignificadoO que fazer
400Corpo inválido: falta phone, message ou file, data de agendamento no passado, URL de webhook inválidaCorrija o corpo; não repita igual
401ID ou token inválidoConfira a URL; a instância pode ter sido excluída
403Instância desativada no painelPeça ao administrador para reativá-la
404Número sem WhatsApp, ou agendamento/rota inexistenteAvise o usuário; não repita
409Instância não conectada, ou agendamento já processadoVerifique /status; reconecte pelo QR Code se preciso
500Falha inesperada, quase sempre no WhatsAppTente de novo após alguns segundos, no máximo 3 vezes

Boas práticas e exemplo completo

O Zapvio usa o protocolo do WhatsApp Web, que não é a API oficial da Meta: envios em massa ou para quem não conhece o número podem levar ao bloqueio do número.

  • Mande mensagens só para quem pediu ou já é cliente, e dê a opção de parar de receber.
  • Peça ao administrador para ligar o atraso (por exemplo, 3 a 8 segundos) ou simular digitação nas instâncias que enviam em volume.
  • Envie um de cada vez e espere a resposta antes do próximo; o Zapvio já enfileira os envios de cada instância.
  • Arquivos: até 25 MB por requisição. Para arquivos grandes, prefira uma URL pública a base64.
  • Guarde o messageId de cada envio para cruzar com os webhooks de status.

Exemplo completo (Node 18+)

Envia uma mensagem, trata os erros comuns e devolve o messageId.

const BASE = process.env.ZAPVIO_URL; // {BASE}/instances/SEU_ID/token/SEU_TOKEN

async function enviarWhatsApp(phone, message) {
  const res = await fetch(`${BASE}/send-text`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ phone, message }),
    signal: AbortSignal.timeout(60_000),
  });
  const data = await res.json();
  if (res.ok) return data.messageId;
  if (res.status === 404) throw new Error(`Sem WhatsApp: ${phone}`);
  if (res.status === 409) throw new Error("WhatsApp desconectado: leia o QR Code de novo");
  throw new Error(`Zapvio ${res.status}: ${data.error}`);
}

const id = await enviarWhatsApp("5543999999999", "Seu pedido #1234 saiu para entrega.");
console.log("enviado", id);

Para testar sem escrever código, use o Playground do painel: ele monta a chamada e mostra o código pronto em cURL, JavaScript e Python.