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.
- 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.
- Conecte o número lendo o QR Code (Conectar o número).
- Envie mensagens com
POST /send-texte os demais endpoints de envio. - 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étodo | Caminho | Retorna |
|---|---|---|
| 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
- Chame
GET /qr-code/image. Na primeira chamada a instância começa a conectar, e ovaluepode virnullpor alguns segundos. - Repita a cada 3 segundos e mostre o
valuenuma tag<img>. O QR muda a cada ~20 segundos, então exiba sempre o mais recente. - O usuário abre o WhatsApp → Aparelhos conectados → Conectar aparelho e lê o código.
- Quando a resposta trouxer
connected: true, pare de consultar. O webhook também recebe o eventoConnectedCallback.
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.
| Caminho | Corpo | Uso |
|---|---|---|
/send-text | phone, message | Texto |
/send-file | phone, file, fileName?, mimetype?, caption? | Qualquer arquivo; imagem vira foto com legenda, o resto vai como documento |
/send-image | phone, image, caption? | Imagem com legenda |
/send-document | phone, document, fileName?, mimetype? | PDF, planilha etc. |
/send-audio | phone, 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
phoneda resposta pode vir sem ele. - Número sem WhatsApp: o envio responde
404com"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étodo | Caminho | Retorna |
|---|---|---|
| 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étodo | Caminho | Corpo / retorno |
|---|---|---|
| POST | /schedule | phone, message?, file?, fileName?, mimetype?, at (obrigatório) → o agendamento, com id e status: "pending" |
| GET | /scheduled | Lista 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-webhookcom{ "value": "https://sua-aplicacao.com/webhook" }. Enviar"value": nullremove. Essa chamada substitui as URLs cadastradas pelo painel.
Evento (type) | Quando chega | Campos principais |
|---|---|---|
ReceivedCallback | Mensagem recebida ou enviada pelo próprio celular | messageId, fromMe, phone, isGroup, participant, senderName, messageType, text, raw |
MessageStatusCallback | Mudança de status de uma mensagem enviada | messageId, phone, status (SENT, RECEIVED, READ, PLAYED) |
ConnectedCallback | O número conectou | connected: true, phone |
DisconnectedCallback | O número desconectou | connected: 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étodo | Caminho | O que faz |
|---|---|---|
| POST | /restart | Fecha e reabre a conexão sem desparear o número. Use quando os envios travarem. |
| POST | /disconnect | Desconecta 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ódigo | Significado | O que fazer |
|---|---|---|
| 400 | Corpo inválido: falta phone, message ou file, data de agendamento no passado, URL de webhook inválida | Corrija o corpo; não repita igual |
| 401 | ID ou token inválido | Confira a URL; a instância pode ter sido excluída |
| 403 | Instância desativada no painel | Peça ao administrador para reativá-la |
| 404 | Número sem WhatsApp, ou agendamento/rota inexistente | Avise o usuário; não repita |
| 409 | Instância não conectada, ou agendamento já processado | Verifique /status; reconecte pelo QR Code se preciso |
| 500 | Falha inesperada, quase sempre no WhatsApp | Tente 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
messageIdde 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.