Introdução
Bem-vindo à API do ZapYou
A API do ZapYou permite que você integre o poder do WhatsApp Business em suas aplicações. Envie mensagens, gerencie instâncias, consulte históricos e muito mais, tudo através de uma API RESTful simples e poderosa.
URL Base
https://server.zapyou.com/v1/api/Formato de Resposta
Todas as respostas são JSON, mas não há um envelope único. O formato depende do grupo do endpoint — não escreva um cliente que assuma success ou data em toda resposta.
// Campanhas, tickets e lookups: envelope { data } (+ pagination nas listagens)
{ "data": [ ... ], "pagination": { "page": 1, "limit": 50, "total": 120, "hasMore": true } }
// Instancias, mensagens, templates e AI agents: objeto cru, sem envelope
{ "id": "507f1f77bcf86cd799439011" }
// Erros: { message } na maior parte da API; { error, details } na validacao Zod
{ "message": "Instance not found" }
{ "error": "INVALID_FIELDS", "details": [ { "path": ["name"], "code": "invalid_type" } ] }Duas exceções valem atenção: GET /instances devolve um objeto indexado por posição ("0", "1"...) em vez de um array, e GET /messages devolve as mensagens agrupadas por instância, não em lista plana.
Autenticação
Company Member Token
GerenciamentoCobre tudo exceto o envio de mensagens. Escopo: a empresa inteira, com cada endpoint checando ainda uma permissão do grupo do membro:
- Instâncias e webhooks
- Consulta de mensagens
- Campanhas e templates Meta
- Tickets (atendimento)
Instance Token
Envio de MensagensUsado somente nos seis endpoints de envio. Cada instância tem seu próprio token, comparado literalmente — sem middleware e sem checagem de permissão:
- Enviar texto
- Enviar imagem
- Enviar vídeo/áudio
- Enviar documento e template
:instanceId das rotas de envio.AI Agent Token
Integração ExternaUsado para integração externa com AI Agents. Cada agente possui seus tokens:
- Conversar com o agente
- Gerenciar sessões
- Obter histórico
- Consultar informações
Como Usar
Authorization: Bearer … para um endpoint de Company Member Token falha em silêncio (o cabeçalho nem é lido); para um endpoint de envio, resulta em 401.# Company Member Token — dois cabecalhos separados, sem "Bearer".
# Usado em: instancias, templates, mensagens, campanhas, tickets, lookups.
member: <CompanyMember.id>
token: <CompanyMember.token>
# Instance Token — valor CRU no Authorization, sem "Bearer".
# Usado apenas nos seis POST /instances/send/*.
Authorization: <Instance.token>
# AI Agent Token — este sim usa Bearer (64 caracteres hexadecimais).
# Usado apenas em /aiagents/*.
Authorization: Bearer <ai-agent-token>Boas Práticas para AI Agent Token
Referência da API
Explore todos os endpoints disponíveis na API do ZapYou, organizados por categoria.
Gerenciamento de Instâncias
6 endpoints
member e token. Não use Bearer. Cada endpoint exige ainda uma permissão do grupo do membro (instance:list, instance:start, instance:stop, instance:update ou instance:webhooks) — o grupo Administrador ignora a checagem. Falta de permissão devolve 401, não 403./instances/send/* e em GET /instances/:instanceId o mesmo parâmetro é o Instance.code (UUID). Trocar um pelo outro devolve 404./instancesListar Instâncias
Retorna todas as instâncias da empresa do token. ATENÇÃO ao formato: a resposta NÃO é um array nem tem envelope — o backend serializa a lista como objeto indexado por posição ("0", "1", "2"...). Use Object.values(resposta) para iterar. Cada item é o registro completo da instância (inclusive o campo token, que é o Instance Token usado nos endpoints de envio), com businessAccountId achatado na raiz (null quando a instância não é meta_cloud). Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:list.
/instances/poweron/:instanceIdLigar Instância
Liga uma instância desligada. Antes de ligar, o backend valida a assinatura: instância sem subscription ACTIVE/TRIALING, com período vencido ou em trial expirado recebe 402 com um code específico. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:start.
/instances/poweroff/:instanceIdDesligar Instância
Desliga uma instância ligada (grava on=false, connected=false e limpa poweredOnAt). A sessão do WhatsApp é preservada — para encerrar a sessão use o endpoint de desconexão. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:stop.
/instances/disconnect/:instanceIdDesconectar Instância
Encerra a sessão do WhatsApp (logout). Em instâncias Baileys o auth_state local é apagado, então a próxima conexão exige novo QR Code. A desconexão é assíncrona: o 200 confirma que o evento foi disparado, não que já concluiu — acompanhe pelo webhook instance:disconnected. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:stop.
/instances/update/:instanceIdAtualizar Nome da Instância
Atualiza o nome da instância. Único campo editável por este endpoint. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:update. Este endpoint não passa por rate limit.
/instances/webhooks/:instanceIdAtualizar URLs de Webhook
Define as URLs de webhook da instância. Chave omitida do corpo permanece inalterada; para limpar uma URL, envie-a como string vazia (""). Cada URL preenchida passa por validação anti-SSRF (protocolo, porta, domínio e faixas de IP privadas/reservadas) antes de ser salva; uma URL reprovada devolve 400 e nenhuma das cinco é gravada. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão instance:webhooks. Este endpoint não passa por rate limit.
Envio de Mensagens
5 endpoints
Authorization com o valor cru do token (sem "Bearer"). Aqui :instanceId é o Instance.code (o UUID da conexão), não o ObjectId. A instância precisa estar ligada e conectada — caso contrário a resposta é 400.{ id, messageId }; instâncias Meta Cloud respondem { message, messageId, timestamp }. Leia sempre messageId, que existe nos dois./instances/send/text/:instanceIdEnviar Mensagem de Texto
Envia uma mensagem de texto para um contato do WhatsApp.
/instances/send/image/:instanceIdEnviar Imagem
Envia uma imagem com legenda opcional para um contato do WhatsApp.
/instances/send/video/:instanceIdEnviar Vídeo
Envia um vídeo para um contato do WhatsApp.
/instances/send/audio/:instanceIdEnviar Áudio
Envia um áudio ou nota de voz (PTT) para um contato do WhatsApp.
/instances/send/document/:instanceIdEnviar Documento
Envia um documento (PDF, DOC, etc) para um contato do WhatsApp.
Consulta de Mensagens
3 endpoints
member e token, sem Bearer. As duas consultas exigem a permissão message:show.instances[].messages. A paginação é por cursor decrescente de ObjectId — repasse nextCursor enquanto hasMore for verdadeiro. Além disso, GET /messages é cacheado por 5 minutos por combinação de filtros: uma mensagem recém-recebida pode não aparecer até o cache expirar ou você chamar POST /messages/clear-cache./messagesListar Mensagens
Lista as mensagens da empresa com paginação por cursor (ObjectId decrescente, da mais recente para a mais antiga). O resultado NÃO é uma lista plana: as mensagens vêm agrupadas por instância, junto de um resumo em stats e do eco dos filtros aplicados. Respostas são cacheadas no Redis por 5 minutos por combinação de filtros — use POST /messages/clear-cache para invalidar. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão message:show.
/messages/:phoneBuscar Mensagens por Telefone
Retorna as mensagens trocadas com um número, agrupadas por instância. Diferente de GET /messages, aqui cada mensagem vem com TODOS os campos do registro e o arquivo completo (include: file) — resposta mais pesada. Não há cache. Toda instância da empresa aparece no array, inclusive as sem mensagens para esse número (com messages vazio). Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão message:show.
/messages/clear-cacheLimpar Cache de Mensagens
Invalida TODO o cache Redis de GET /messages da empresa do token (todas as combinações de filtro). Não recebe corpo — enviar um é inofensivo, mas ignorado; não há como limpar o cache de uma instância específica. Este endpoint não checa permissão além da autenticação e não passa por rate limit.
Campanhas
37 endpoints
member e token, sem Bearer. Cada rota exige uma permissão do grupo — campaign:list, campaign:show, campaign:create, campaign:edit ou campaign:delete — e a resposta 403 nomeia a permissão faltante em requiredPermission.{ data, pagination: { page, limit, total, hasMore } } — use hasMore para decidir se há próxima página. DELETE /campaigns/:campaignId não apaga: apenas desativa, e campanhas desativadas somem da listagem./campaignsListar Campanhas
Lista as campanhas ativas da empresa (as desativadas via DELETE /campaigns/:campaignId nunca aparecem), da mais recente para a mais antiga, com paginação por página. Cada item é um resumo — para o objeto completo com contagens, use GET /campaigns/:campaignId. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão campaign:list.
/departmentsListar Departamentos
Lookup dos departamentos ativos da empresa, para preencher o campo departmentId ao configurar o roteamento de respostas de campanha (PUT /campaigns/:campaignId/reply-routing). Devolve apenas id e nome, sem paginação. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão campaign:show.
/membersListar Atendentes
Lookup dos membros da empresa, para preencher o campo memberId ao rotear respostas de campanha para um atendente específico. O id devolvido é o User.id (não o CompanyMember.id). Sem paginação e sem filtro por status. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão campaign:show.
/campaignsCriar Campanha
Cria uma nova campanha. Use type "default" para campanhas comuns (texto/mídia) ou "meta_template" para campanhas baseadas em templates Meta aprovados.
/campaigns/:campaignIdObter Campanha
Retorna os detalhes completos de uma campanha pelo seu ID.
/campaigns/scheduledListar Campanhas Agendadas
Retorna as campanhas com agendamento ativo (status "scheduled").
/campaigns/:campaignId/nameRenomear Campanha
Altera o nome de uma campanha existente.
/campaigns/:campaignId/intervalAlterar Intervalo de Envio
Define o intervalo (delay) em segundos entre cada disparo da campanha.
/campaigns/:campaignId/duplicateDuplicar Campanha
Cria uma cópia da campanha. Use duplicateType para escolher o que copiar: "full" (tudo), "messages-only" (apenas mensagens) ou "payloads-only" (apenas destinatários).
/campaigns/:campaignIdDesativar Campanha
Desativa (remove) uma campanha pelo seu ID.
/campaigns/:campaignId/messages/modelsListar Modelos de Mensagem
Retorna os modelos de mensagem (texto/mídia) configurados na campanha, ordenados por "order".
/campaigns/:campaignId/messages/modelsAdicionar Modelo de Mensagem
Adiciona um modelo de mensagem à campanha. Pode ser um modelo comum (texto/mídia, com type + options) ou o anexo de um template Meta aprovado (type "template" + templateId). Campanhas meta_template aceitam apenas 1 template.
/campaigns/:campaignId/messages/models/:messageIdAtualizar Modelo de Mensagem
Atualiza um modelo de mensagem existente. Usa o mesmo schema do create (type + options obrigatórios).
/campaigns/:campaignId/messages/models/:messageId/orderReordenar Modelo de Mensagem
Altera a posição (order) de um modelo de mensagem na sequência de envio.
/campaigns/:campaignId/messages/models/:messageIdRemover Modelo de Mensagem
Remove um modelo de mensagem da campanha.
/campaigns/:campaignId/template/previewPré-visualizar Template
Renderiza o template Meta da campanha usando os campos de um destinatário existente, identificado pelo índice (payloadIndex).
/campaigns/:campaignId/template/variablesListar Variáveis do Template
Retorna as variáveis declaradas pelo template Meta anexado à campanha, aninhadas por componente (header/body/buttons), além do mapeamento atual (currentMapping), dos campos disponíveis dos destinatários (availableFields) e da mídia de header (headerMediaType, staticHeaderMedia). O objeto `variables` segue o mesmo shape esperado pelo `variableMapping` em PUT /template/mapping.
/campaigns/:campaignId/template/mappingMapear Variáveis do Template
Define o mapeamento entre as variáveis do template Meta e os campos dos destinatários. O `variableMapping` é aninhado por componente (header/body/buttons); cada sub-objeto é um Record<string,string> (índice da variável → nome da coluna). Botões usam a chave "índiceBotão_índiceVar" (ex.: "0_1") e o header de mídia dinâmica usa a chave "media".
/campaigns/:campaignId/mediaUpload de Mídia da Campanha
Envia um arquivo e devolve uma URL pública utilizável no header de mídia do template (`staticHeaderMedia`) ou em uma ação de botão (`buttonActions[].url`). O upload **não** grava configuração: persista a URL você mesmo, via `PUT /campaigns/:campaignId/template/mapping` ou `PUT /campaigns/:campaignId/messages/models/:messageId`. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer"). A resposta vem **fora** do envelope `{ data }` usado no restante da API pública. A URL gerada é pública — é o servidor da Meta que baixa o arquivo a cada disparo.
/campaigns/:campaignId/payloadsListar Destinatários
Retorna os destinatários (payloads) da campanha de forma paginada. Cada payload contém telefone e campos personalizados.
/campaigns/:campaignId/payloadsAdicionar Destinatários
Adiciona destinatários à campanha em lote. Cada item é um objeto com o telefone (campo definido por phoneKey, default "phone") e campos personalizados.
/campaigns/:campaignId/payloads/import-urlImportar Destinatários por URL
Importa destinatários a partir de um arquivo acessível por URL (ex.: CSV). O backend baixa o arquivo, faz o parse e adiciona os destinatários à campanha.
/campaigns/:campaignId/contactsListar Contatos
Retorna os contatos da campanha de forma paginada (telefone + status ativo).
/campaigns/:campaignId/instancesDefinir Instâncias da Campanha
Define as instâncias (conexões WhatsApp) usadas para enviar a campanha. Substitui a lista atual.
/campaigns/:campaignId/testingTestar Campanha
Envia a sequência de mensagens da campanha para um número de teste, sem afetar a lista real de destinatários.
/campaigns/:campaignId/startIniciar Campanha
Inicia o disparo da campanha. Envie o header Idempotency-Key (UUID único por clique) para evitar inícios duplicados.
/campaigns/:campaignId/pausePausar Campanha
Pausa uma campanha em andamento.
/campaigns/:campaignId/resumeRetomar Campanha
Retoma uma campanha que estava pausada.
/campaigns/:campaignId/schedule/startAgendar Início
Agenda o início automático da campanha para uma data/hora (ISO 8601).
/campaigns/:campaignId/schedule/pauseAgendar Pausa
Agenda a pausa automática da campanha para uma data/hora (ISO 8601).
/campaigns/:campaignId/schedule/resumeAgendar Retomada
Agenda a retomada automática da campanha para uma data/hora (ISO 8601).
/campaigns/:campaignId/scheduleAtualizar Agendamento Completo
Define em uma única chamada todas as janelas de agendamento (início, pausa, retomada) e o auto-agendamento.
/campaigns/:campaignId/scheduleCancelar Agendamento
Remove todas as janelas de agendamento da campanha, desativando o auto-agendamento.
/campaigns/:campaignId/reply-routingConfigurar Roteamento de Respostas
Define para onde as respostas dos contatos são roteadas: fila ("queue"), departamento ("department") ou atendente ("member"). Envie replyRouting: null para desabilitar.
/campaigns/:campaignId/reply-routingDesabilitar Roteamento de Respostas
Desabilita o roteamento de respostas da campanha. Usa o mesmo endpoint do reply-routing, enviando replyRouting: null no corpo.
/campaigns/:campaignId/submissionsListar Envios
Retorna os envios (submissions) da campanha de forma paginada, com status individual de cada disparo. Pode filtrar por status.
/campaigns/:campaignId/metricsObter Métricas da Campanha
Retorna as métricas consolidadas da campanha: totais por status, percentual de progresso e taxas de entrega e leitura.
Meta Templates
4 endpoints
member: <CompanyMember.id> e token: <CompanyMember.token>. Não use Bearer.Authorization com o valor cru do token (sem "Bearer"). Enviar Bearer <token> causa 401.:instanceId é o instance.code (não o ObjectId). Para filtrar templates por instância, use ?instanceId=<code>.variables da listagem traz apenas as variáveis do BODY. Um template com variável no header ou na URL de um botão não as expõe ali — elas só aparecem em expectedVariables, que existe somente em GET /templates/:id. Quem monta o formulário de preenchimento a partir da listagem perde essas variáveis silenciosamente e recebe 422 MISSING_VARIABLES no envio, sem entender por quê.Recomendação: liste com GET /templates, mas monte o formulário com GET /templates/:id.
Renderizando o preview do template com components
Estrutura crua do templatecomponents carrega a estrutura crua do template — header, body, footer e botões, com os exemplos de preenchimento vindos da Meta. É o que permite mostrar ao usuário como a mensagem vai ficar antes de disparar. Ele vem tanto na listagem quanto no detalhe.
- Percorra o array por
type:HEADER,BODY,FOOTEReBUTTONS. A ordem no array é a ordem de exibição. - No
HEADER, useformatpara saber se é texto (TEXT) ou mídia (IMAGE,VIDEO,DOCUMENT). Esse mesmoformatdefine otypedoheaderMediano envio. - Use
examplepara pré-preencher a visualização com valores realistas — o BODY trazexample.body_text[0], um array na ordem das variáveis.
// Substitui {{1}}, {{2}}... pelos valores informados
const fill = (text, values) =>
text.replace(/\{\{(\d+)\}\}/g, (_, i) => values[i] ?? '{{' + i + '}}');
function renderPreview(template, values = {}) {
const preview = { header: null, body: '', footer: '', buttons: [] };
for (const c of template.components) {
switch (c.type) {
case 'HEADER':
// format diz se o header e texto ou midia
preview.header =
c.format === 'TEXT'
? { kind: 'text', value: fill(c.text, values) }
: { kind: 'media', mediaType: c.format }; // IMAGE | VIDEO | DOCUMENT
break;
case 'BODY':
preview.body = fill(c.text, values);
break;
case 'FOOTER':
preview.footer = c.text; // footer nao aceita variavel
break;
case 'BUTTONS':
preview.buttons = c.buttons.map((b) => ({ type: b.type, text: b.text }));
break;
}
}
return preview;
}
// Pre-preenchendo com os exemplos que a Meta devolve:
const body = template.components.find((c) => c.type === 'BODY');
const sample = body?.example?.body_text?.[0] ?? []; // ["Joao", "12345"]
const values = Object.fromEntries(sample.map((v, i) => [String(i + 1), v]));
renderPreview(template, values);?instanceId=<code> de uma instância baileys ou sem WABA devolve uma lista vazia — e não os templates da empresa inteira. É proposital (evita vazar templates de outro WABA), mas costuma parecer bug na primeira integração./templatesListar Templates WABA
Lista os templates Meta Cloud da empresa, com filtros e paginação. Cada item traz components (a estrutura crua do template — header, body, footer e botões, com os exemplos vindos da Meta), que é o que permite renderizar um preview antes de disparar. Atenção: o campo variables lista APENAS as variáveis do BODY — variáveis de header ou de URL de botão não aparecem aqui; para montar o formulário de envio, use GET /templates/:id (expectedVariables). Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige permissão instance:list.
/templates/:idDetalhe de Template
Retorna um template específico — os mesmos campos de um item da lista (inclusive components, plano na raiz, sem o envelope data), acrescidos de expectedVariables. Este é o ÚNICO endpoint que expõe expectedVariables, o contrato completo de variáveis (header, body, botões e total). Liste com GET /templates, mas monte o formulário de envio com GET /templates/:id: quem deriva o formulário do campo variables da listagem perde as variáveis de header e de botão silenciosamente e recebe 422 MISSING_VARIABLES no envio. Autenticação por Company Member Token (cabeçalhos member + token) — exige permissão instance:list.
/instances/:instanceIdDetalhe de Instância
Retorna os dados públicos (seguros) de uma instância. Nunca expõe access token, segredos ou credenciais. Autenticação por Company Member Token (cabeçalhos member + token) — exige permissão instance:show.
/instances/send/template/:instanceIdEnviar Mensagem via Template
Envia uma mensagem usando um template Meta Cloud aprovado. Requer instância meta_cloud ligada e conectada. A resposta é SÍNCRONA: o backend aguarda o resultado real do envio (ou 30s de timeout) antes de responder — não há polling a fazer. Autenticação por Instance Token: cabeçalho Authorization com o valor CRU do token (sem "Bearer") — enviar "Bearer <token>" causa 401.
AI Agents
5 endpoints
AI Agent Token
Integração ExternaTokens exclusivos para cada AI Agent, permitindo acesso programático sem autenticação de usuário. Crie chatbots inteligentes, assistentes virtuais e automações.
/aiagents/chatChat com AI Agent
Envia uma mensagem para o AI Agent e recebe a resposta. Usa o contexto da sessão para manter a conversa.
/aiagents/agentInformações do Agente
Retorna informações básicas do AI Agent associado ao token.
/aiagents/widget/configConfiguração do Widget
Devolve os dados públicos usados pela página hospedada do widget de chat (widget.zapyou.com). Só responde 200 se o widget estiver habilitado para ESTE token — habilite em AI Agents → [Agente] → Tokens. O script carregador do widget é servido pela própria API, em https://server.zapyou.com/v1/widget/loader.js.
/aiagents/sessions/:sessionIdBuscar Histórico da Sessão
Retorna o histórico de uma sessão. As sessões vivem no Redis com TTL de 24 horas a partir da última mensagem e guardam no máximo as 50 mensagens mais recentes (janela deslizante) — este endpoint devolve até 50. Sessão inexistente ou expirada NÃO é erro: responde 200 com messages vazio e totalMessages 0. O escopo é o token: uma sessão criada por outro token não é visível aqui.
/aiagents/sessions/:sessionIdLimpar Sessão
Apaga o histórico da sessão no Redis, permitindo recomeçar a conversa com o mesmo ID. Devolve 404 quando não havia nada para apagar (sessão inexistente ou já expirada).
Tickets (Atendimento)
4 endpoints
API somente-leitura para montar dashboards externos de atendimento: listar tickets com filtros e paginação, ver o detalhe de um ticket, o histórico de movimentação e estatísticas de mensagens. O escopo é a empresa inteira do token — diferente da tela interna, não há recorte por canais do membro nem por regras de fila.
member: <CompanyMember.id> e token: <CompanyMember.token>. Não use Bearer. Todos os 4 endpoints exigem a permissão attendance:reports:view./stats devolve texto ou mídia — apenas contagens, tipos, status de entrega e tempos. Para o conteúdo, use GET /messages.sortBy=closedAt sempre junto com status=CLOSED (índice dedicado). Evite sortBy=updatedAt em consultas amplas — cai em sort em memória. Sorts seguros em qualquer filtro: lastMessageAt (default) e createdAt./ticketsListar Tickets
Lista os tickets (atendimentos) da empresa com filtros ricos e paginação por página. Autenticação por Company Member Token (cabeçalhos member + token, sem "Bearer") — exige a permissão attendance:reports:view.
/tickets/:ticketIdDetalhe de um Ticket
Retorna um ticket específico. Mesmo objeto da listagem, sem messageCount, acrescido de customFields, unreadCount, transferredFromUserId, transferReason e um contato mais completo. Autenticação por Company Member Token (cabeçalhos member + token) — exige attendance:reports:view.
/tickets/:ticketId/historyHistórico de Movimentação
Retorna o histórico de movimentação do ticket (mudanças de status, transferências, notas). Autenticação por Company Member Token (cabeçalhos member + token) — exige attendance:reports:view.
/tickets/:ticketId/statsEstatísticas de Mensagens
Retorna estatísticas de mensagens do ticket: contagens por remetente/tipo/status de entrega e tempos. NUNCA inclui conteúdo de mensagem (texto ou mídia). Autenticação por Company Member Token (cabeçalhos member + token) — exige attendance:reports:view.
Webhooks
O que são Webhooks?
Webhooks são notificações HTTP POST enviadas automaticamente para URLs configuradas quando eventos específicos ocorrem no ZapYou. Use webhooks para receber atualizações em tempo real sobre mensagens, status e conexões.
connectedURL recebe event: "instance:connected" (com o prefixo instance:), e disconnectedURL recebe "instance:disconnected". Já statusMessageURL recebe "ack" em instâncias Baileys e "status" em instâncias Meta Cloud — com valores de status diferentes em cada uma.GET /messages.messageURLDisparado a cada mensagem recebida ou enviada — é o webhook de maior frequência. O campo event vale sempre "message", mas o payload difere conforme o provider da instância: instâncias Meta Cloud incluem provider: "meta_cloud" e um objeto file com mediaId/mimeType, enquanto instâncias Baileys não enviam provider e trazem mentions e registryType. Entrega: Baileys chama uma única vez, com timeout de 5s e sem retentativa; Meta Cloud usa timeout de 10s com até 3 retentativas em backoff exponencial. Uma falha na entrega não é reprocessada depois — trate o webhook como best-effort e reconcilie por GET /messages quando precisar de garantia.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Sempre "message" |
datetime | string | Timestamp da mensagem em ISO 8601 |
payload.provider | string | Presente APENAS em instâncias Meta Cloud, com o valor "meta_cloud". Ausente em Baileys |
payload.instance | string | Instance.id (ObjectId de 24 caracteres) |
payload.code | string | Instance.code (UUID da instância) |
payload.message | string | ID da mensagem no WhatsApp (wamid em Meta Cloud) |
payload.type | string | Tipo da mensagem: text, image, video, gif, audio, document, sticker, location, contact... |
payload.fromMe | boolean | true quando a mensagem saiu desta linha |
payload.source | string | Somente em ecos do Meta Cloud (fromMe: true): "whatsapp_app" |
payload.remotePhone | string | Número do contato remoto |
payload.connectedPhone | string | Número da instância conectada |
payload.text | string | Texto da mensagem ou legenda da mídia. String vazia quando não há texto |
payload.file | object | Baileys: { is } quando não há arquivo; { is, name, type, mimeType, size, url } quando há. Meta Cloud: { is, mediaId, mimeType, caption, fileName, url } |
payload.group | object | { is, id } — id é string vazia fora de grupo. Meta Cloud sempre envia { is: false, id: "" } |
payload.forwarded | object | { is, score } — o padrão é { is: false, score: 0 } |
payload.quoted | object | { is, id } — id é o stanzaId da mensagem citada; o padrão é { is: false, id: "" } |
payload.mentions | array | Apenas em Baileys: JIDs mencionados na mensagem |
payload.contacts | array | Cartões de contato anexados |
payload.localization | object | Objeto vazio quando não há localização; { degreesLatitude, degreesLongitude, live } quando há |
payload.status | string | Baileys: received, error, pending, server, delivery, read ou played. Meta Cloud: RECEIVED (entrada) ou SENT (eco) |
payload.pushName | string | Nome do contato no WhatsApp. Cai para "Unknown" quando indisponível |
payload.createdAt | string | Timestamp da mensagem em ISO 8601 |
payload.registryType | string | Apenas em Baileys: tipo bruto do evento interno de origem |
Example Payload
{
"event": "message",
"datetime": "2026-01-08T14:30:45.000Z",
"payload": {
"instance": "507f1f77bcf86cd799439011",
"code": "9f8b4c21-5d3e-4a17-9c62-2b7e0f5a1d38",
"message": "3EB0F7C8E2D4A3B1",
"type": "text",
"fromMe": false,
"remotePhone": "551199999000",
"connectedPhone": "551151183618",
"text": "Olá, gostaria de informações sobre produtos",
"file": {
"is": false
},
"group": {
"is": false,
"id": ""
},
"forwarded": {
"is": false,
"score": 0
},
"quoted": {
"is": false,
"id": ""
},
"mentions": [],
"contacts": [],
"localization": {},
"status": "received",
"pushName": "João Silva",
"createdAt": "2026-01-08T14:30:45.000Z",
"registryType": "text"
}
}statusMessageURLDisparado quando o status de entrega de uma mensagem muda. ATENÇÃO: o nome do evento e o formato do payload dependem do provider. Baileys envia event "ack" com um payload mínimo; Meta Cloud envia event "status" com os metadados da Meta (destinatário, conversa, tarifação e erro). Os valores de status também não coincidem entre os dois — não trate o campo como um enum único.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | "ack" em instâncias Baileys; "status" em instâncias Meta Cloud |
datetime | string | Momento do processamento do evento, em ISO 8601 |
payload.instance | string | Instance.id (ObjectId de 24 caracteres) |
payload.code | string | Instance.code (UUID da instância) |
payload.message | string | ID da mensagem (wamid em Meta Cloud) |
payload.status | string | Baileys: error, pending, server, delivery, read ou played. Meta Cloud: os valores da Meta (sent, delivered, read, failed) |
payload.recipient | string | Somente Meta Cloud: número do destinatário |
payload.timestamp | number | Somente Meta Cloud: timestamp Unix informado pela Meta |
payload.conversationId | string | Somente Meta Cloud: ID da conversa tarifada |
payload.pricing | object | Somente Meta Cloud: dados de tarifação da conversa |
payload.error | object | Somente Meta Cloud: detalhes do erro quando status é failed |
Example Payload
{
"event": "ack",
"datetime": "2026-01-08T14:31:00.000Z",
"payload": {
"instance": "507f1f77bcf86cd799439011",
"code": "9f8b4c21-5d3e-4a17-9c62-2b7e0f5a1d38",
"message": "3EB0F7C8E2D4A3B1",
"status": "read"
}
}connectedURLDisparado quando a instância conclui a conexão com o WhatsApp. Evento de baixa frequência. O nome do evento é "instance:connected" — com o prefixo instance:, não apenas "connected". O payload traz somente a identificação da instância; para saber o número conectado, consulte GET /instances/:instanceId após receber o evento.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Sempre "instance:connected" |
datetime | string | Momento do evento em ISO 8601 |
payload.instance | string | Instance.id (ObjectId de 24 caracteres) |
payload.code | string | Instance.code (UUID da instância) |
Example Payload
{
"event": "instance:connected",
"datetime": "2026-01-08T14:30:00.000Z",
"payload": {
"instance": "507f1f77bcf86cd799439011",
"code": "9f8b4c21-5d3e-4a17-9c62-2b7e0f5a1d38"
}
}disconnectedURLDisparado quando a instância perde a conexão com o WhatsApp — por logout via API, sessão expirada ou queda de conexão. Evento de baixa frequência. O nome do evento é "instance:disconnected" e o payload NÃO informa o motivo da desconexão: traz apenas a identificação da instância.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Sempre "instance:disconnected" |
datetime | string | Momento do evento em ISO 8601 |
payload.instance | string | Instance.id (ObjectId de 24 caracteres) |
payload.code | string | Instance.code (UUID da instância) |
Example Payload
{
"event": "instance:disconnected",
"datetime": "2026-01-08T15:00:00.000Z",
"payload": {
"instance": "507f1f77bcf86cd799439011",
"code": "9f8b4c21-5d3e-4a17-9c62-2b7e0f5a1d38"
}
}templateButtonURLDisparado quando um contato toca um botão de RESPOSTA RÁPIDA em uma mensagem de template. Exclusivo de instâncias Meta Cloud — instâncias Baileys não enviam template, então a URL nunca produz evento nelas. ATENÇÃO ao limite da plataforma: botões de URL, telefone e copiar código NÃO geram evento algum, porque a Meta não reporta esses toques ao servidor; respostas de Flow também estão fora do escopo. Este é o único webhook assinado da ZapYou: cada requisição traz o cabeçalho X-ZapYou-Signature no formato sha256=<hex>, um HMAC-SHA256 do corpo CRU (antes de qualquer parse) usando o token da instância como chave. Entrega: timeout de 10s e até 4 tentativas com backoff exponencial; diferente dos demais webhooks, qualquer resposta com status maior ou igual a 400 conta como falha e é retentada — se o seu endpoint devolve 401 ao rejeitar uma assinatura, isso aparece como falha de entrega e não como sucesso. Use payload.click.messageId como chave de idempotência.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Sempre "template:button" |
datetime | string | Momento em que a ZapYou processou o clique, em ISO 8601 |
payload.provider | string | Sempre "meta_cloud" |
payload.click.messageId | string | wamid da mensagem do clique — use como chave de idempotência |
payload.click.clickedAt | string | Momento do clique informado pela Meta, em ISO 8601 |
payload.connection.instance | string | Instance.id (ObjectId de 24 caracteres) |
payload.connection.code | string | Instance.code (UUID da instância) |
payload.connection.name | string | Nome da instância na ZapYou |
payload.connection.connectedPhone | string | Número da instância conectada. null quando não cadastrado |
payload.connection.phoneNumberId | string | phone_number_id do número na Meta |
payload.connection.businessAccountId | string | WABA ID da conta. null quando indisponível |
payload.contact.id | string | Contact.id na ZapYou. null quando o contato não está cadastrado |
payload.contact.phone | string | Telefone do contato, normalizado |
payload.contact.pushName | string | Nome do contato no WhatsApp. null quando indisponível |
payload.contact.name | string | Nome cadastrado na ZapYou. null quando o contato não está cadastrado |
payload.contact.email | string | E-mail cadastrado. null quando ausente |
payload.contact.profilePicUrl | string | Foto de perfil. null quando ausente |
payload.contact.tags | array | Tags do contato como { id, name, color }. Array vazio quando não há. Campos personalizados (customFields) NÃO são enviados |
payload.template.messageId | string | wamid da mensagem de template original. Sempre presente |
payload.template.sentAt | string | Quando o template foi enviado, em ISO 8601. Presente sempre que o envio deixou registro — inclusive com resolved: false. NÃO use este campo para inferir se o template foi resolvido |
payload.template.id | string | MetaTemplate.id na ZapYou. null quando resolved é false |
payload.template.metaId | string | ID do template na Meta. null quando resolved é false |
payload.template.name | string | Nome do template. null quando resolved é false |
payload.template.language | string | Idioma do template (ex.: pt_BR). null quando resolved é false |
payload.template.category | string | MARKETING, UTILITY ou AUTHENTICATION. null quando resolved é false |
payload.template.resolved | boolean | ÚNICO campo que indica se o template foi identificado. false quando o envio é anterior à v2.45.0 ou quando o registro não foi encontrado — nesse caso o evento é entregue mesmo assim, com os dados de clique, conexão, contato e botão |
payload.button.type | string | Sempre "QUICK_REPLY" quando identificado. null quando resolved é false |
payload.button.text | string | Rótulo do botão tocado, como veio da Meta. Sempre presente |
payload.button.payload | string | Payload do botão. Em disparos de campanha vem como btn_<índice>; nos demais envios a Meta ecoa o próprio rótulo do botão |
payload.button.index | number | Posição do botão no template (base 0). null quando resolved é false ou quando o botão é ambíguo (dois botões com o mesmo rótulo) |
payload.context.campaign | object | { id, name } quando o template saiu de uma campanha; null caso contrário. Sempre presente como chave |
payload.context.ticket | object | { id, protocol } quando o template pertence a um atendimento; null caso contrário. Sempre presente como chave |
Example Payload
{
"event": "template:button",
"datetime": "2026-09-03T14:22:10.000Z",
"payload": {
"provider": "meta_cloud",
"click": {
"messageId": "wamid.HBgNNTUxMTk5OTk5MDAwFQ",
"clickedAt": "2026-09-03T14:22:09.000Z"
},
"connection": {
"instance": "507f1f77bcf86cd799439011",
"code": "9f8b4c21-5d3e-4a17-9c62-2b7e0f5a1d38",
"name": "Comercial",
"connectedPhone": "551151183618",
"phoneNumberId": "123456789012345",
"businessAccountId": "987654321098765"
},
"contact": {
"id": "6512f8a1b2c3d4e5f6a7b8c9",
"phone": "551199999000",
"pushName": "João",
"name": "João Silva",
"email": "[email protected]",
"profilePicUrl": "https://cdn.exemplo.com/joao.jpg",
"tags": [
{
"id": "6512bb00000000000000bb01",
"name": "Lead quente",
"color": "#FF5722"
}
]
},
"template": {
"messageId": "wamid.HBgMNTUxMTk5OTk5MDAwFQ",
"sentAt": "2026-09-03T09:00:00.000Z",
"id": "6512aa00000000000000aa01",
"metaId": "123456789012345",
"name": "promo_setembro",
"language": "pt_BR",
"category": "MARKETING",
"resolved": true
},
"button": {
"type": "QUICK_REPLY",
"text": "Quero saber mais",
"payload": "btn_0",
"index": 0
},
"context": {
"campaign": {
"id": "6512cc00000000000000cc01",
"name": "Promo Setembro"
},
"ticket": null
}
}
}Códigos de Erro
A API do ZapYou usa códigos de status HTTP convencionais para indicar sucesso ou falha de uma requisição. Códigos na faixa 2xx indicam sucesso, códigos na faixa 4xx indicam erro do cliente, e códigos na faixa 5xx indicam erro do servidor.
| Code | Status | Name | Description | Solution |
|---|---|---|---|---|
— | 200 | Sucesso | Requisição processada com sucesso | Nenhuma ação necessária |
— | 201 | Criado | Recurso criado. Devolvido apenas por POST /campaigns | Nenhuma ação necessária |
(sem code) | 400 | Requisição Inválida | Campos obrigatórios ausentes ou inválidos nos endpoints de envio e de instância. O corpo traz apenas { message }, ex.: "Invalid fields", "Number not found", "Invalid type. Must be 'PN' or 'LID'" | Confira os campos obrigatórios do endpoint e o formato do número |
INVALID_FIELDS | 400 | Campos Inválidos (validação Zod) | Usado nos endpoints validados por Zod (campanhas, tickets e envio de template). O corpo é { error: "INVALID_FIELDS", details: [...] }, onde details são as issues cruas do Zod (path, code, message) — não há campo message no nível raiz | Percorra details[].path para mapear cada erro ao campo correspondente do formulário |
TAG_FILTER_TOO_BROAD | 400 | Filtro de Tags Muito Amplo | Na listagem de tickets, o tagIds enviado casa mais de 5.000 contatos; a requisição é rejeitada, não truncada | Restrinja o filtro (menos tags / tags mais específicas) ou combine com contactId, channelId, departmentId ou um range de datas |
(sem code) | 401 | Não Autorizado | Cobre dois casos distintos: credenciais inválidas (cabeçalhos member/token ausentes ou não conferem; Authorization diferente do token da instância; Bearer de AI Agent inválido ou expirado) E falta de permissão nos endpoints de instância e mensagem, que respondem 401 — não 403 — com { message: "User without permission" } | Verifique o esquema de autenticação do endpoint (são três, incompatíveis entre si) e, se as credenciais estiverem certas, a permissão do grupo do membro |
SUBSCRIPTION_REQUIRED | 402 | Assinatura Necessária | POST /instances/poweron/:instanceId em instância sem subscription ACTIVE ou TRIALING | Regularize a assinatura da instância antes de ligá-la |
SUBSCRIPTION_EXPIRED | 402 | Assinatura Expirada | POST /instances/poweron/:instanceId com o período da assinatura vencido | Renove a assinatura da instância |
TRIAL_EXPIRED | 402 | Trial Expirado | POST /instances/poweron/:instanceId em instância de teste com prazo vencido | Adicione um método de pagamento para converter o trial em assinatura |
Insufficient Balance | 402 | Saldo de IA Insuficiente | POST /aiagents/chat sem saldo de IA na empresa para reservar o custo do embedding ou da geração. É o ÚNICO 402 por saldo da API — os endpoints de envio de mensagem nunca retornam 402 | Recarregue o saldo de IA da empresa no painel |
(sem code) | 403 | Permissão Insuficiente | Usado pelos endpoints de campanhas, tickets e lookups. O corpo traz { message: "Insufficient permissions", requiredPermission: "<código>" }, que nomeia exatamente a permissão faltante | Conceda a permissão indicada em requiredPermission ao grupo do membro |
Forbidden | 403 | Widget Desabilitado | GET /aiagents/widget/config com o widget desativado para o token informado | Habilite o widget para esse token em AI Agents → [Agente] → Tokens |
(sem code) | 404 | Não Encontrado | Recurso inexistente ou pertencente a outra empresa — a API responde 404 nos dois casos, de propósito, para não confirmar a existência de IDs alheios | Confira o ID e se ele pertence à empresa do token |
TEMPLATE_NOT_FOUND | 404 | Template Não Encontrado | O template não existe para essa empresa/idioma (envio via template) | Confira templateName e language; eles precisam bater com um template aprovado |
TEMPLATE_NOT_SUPPORTED | 422 | Template Não Suportado | A instância é Baileys; templates só funcionam em instâncias Meta Cloud | Use uma instância meta_cloud para enviar via template |
TEMPLATE_NOT_APPROVED | 422 | Template Não Aprovado | O template não está com status APPROVED (veja rejectedReason) | Aguarde aprovação do template na Meta ou ajuste e reenvie para revisão |
MISSING_VARIABLES | 422 | Variáveis Ausentes | Faltam variáveis exigidas pelo template (body, header ou buttons) | Use GET /templates/:id → expectedVariables para montar o payload completo |
(sem code) | 422 | Entidade Não Processável | Regra de negócio violada em campanhas, ex.: "One or more instances are invalid" ao associar instâncias que não pertencem à empresa | Verifique os IDs enviados e o estado atual da campanha |
(sem code) | 409 | Conflito | Ação de campanha incompatível com o estado atual ("Campaign already sending") ou reenvio detectado pelo header Idempotency-Key ("Duplicate request") | Consulte o status atual em GET /campaigns/:campaignId antes de repetir a ação |
RATE_LIMIT_EXCEEDED | 429 | Limite de Taxa Excedido | Muitas requisições na janela. O corpo traz { error, retryAfter } e, em alguns buckets, message. Os cabeçalhos padrão RateLimit-* acompanham a resposta | Aguarde os segundos indicados em retryAfter e implemente backoff exponencial |
(sem code) | 500 | Erro Interno do Servidor | Erro inesperado. A mensagem crua fica no log do servidor, nunca na resposta | Tente novamente mais tarde ou contate o suporte |
(sem code) | 503 | Instância Indisponível | Endpoints de envio: a instância existe mas o socket não está autenticado no momento (Baileys) ou a instância Meta Cloud não está ativa | Confirme o estado da conexão e, se necessário, desligue e ligue a instância |
(sem code) | 504 | Timeout de Envio | Sem resposta do envio em 30s. Ocorre apenas em POST /instances/send/template/:instanceId | A mensagem pode ter sido enviada mesmo assim — confirme antes de repetir, para não duplicar o disparo |
Limites de Taxa
Limites de Requisição
Para garantir a qualidade do serviço para todos os usuários, a API do ZapYou implementa limites de taxa nas requisições. Se você exceder o limite, receberá um erro 429 Too Many Requests.
POST /instances/send/* (texto, imagem, vídeo, áudio, documento e template)Chave = valor do header Authorization + instanceId da rota. Requisições com o header x-internal-request: true são ignoradas pelo contador
POST /aiagents/chatMesmo bucket dos envios de mensagem (30/min, não 60). Chave = o Bearer token do AI Agent
GET /aiagents/agent, /aiagents/sessions/:sessionId, /aiagents/widget/config e DELETE /aiagents/sessions/:sessionIdBucket geral da API. Chave = o Bearer token do AI Agent
GET /instances, /instances/:instanceId, /templates, /templates/:id, /messages, /messages/:phone e POST /instances/poweron|poweroff|disconnectBucket geral da API. A chave é o header Authorization — que estes endpoints NÃO usam (autenticam por member + token). Na prática, portanto, o limite é por IP de origem, compartilhado entre todos os tokens que saem do mesmo IP
Endpoints de campanhas (/campaigns/*), GET /departments e GET /membersChave = companyId do token. Bucket próprio, que não compete com o dos tickets
GET /tickets/*Chave = companyId do token. Bucket separado, dimensionado para dashboards com várias widgets
PUT /instances/update/:instanceId, PUT /instances/webhooks/:instanceId e POST /messages/clear-cacheEstes três endpoints não têm rate limit aplicado; ainda assim, use-os com parcimônia
