zapbot https://api.zapbot.online

Documentação da API

Uma API HTTP com o contrato que o mercado já usa: mesmos caminhos, mesmo formato de credenciais e mesmo formato de webhook. Se você já integra WhatsApp por HTTP, basta trocar a URL base.

Visão geral

Toda operação de um número acontece dentro de uma instância. Cada instância tem um instanceId e um token, ambos no caminho da URL. O cabeçalho opcional Client-Token adiciona uma camada de segurança em nível de conta.

Uma instância equivale a um número de WhatsApp conectado. Elas são isoladas entre si: token, webhook e sessão são independentes.

Autenticação

Credencial Onde vai Quando
instanceId + token Caminho da URL Toda rota de instância. Identificam e autorizam.
Client-Token Header HTTP Opcional. Se definido na instância, passa a ser obrigatório em toda requisição.
Authorization: Bearer Header HTTP Apenas nas rotas administrativas /admin/*.

URL base

https://api.zapbot.online/instances/{instanceId}/token/{token}/{rota}
Migrando de outro provedor? Troque só o domínio na URL base. Corpo, headers e respostas permanecem idênticos — nenhuma linha da sua integração muda.

Erros

Respostas de erro trazem o código HTTP e um corpo JSON com a descrição.

Código Significado
400 Corpo inválido ou campo obrigatório ausente.
401 Credencial inválida: token, Client-Token ou Bearer administrativo.
404 Instância inexistente ou token do caminho inválido. Os dois devolvem a mesma coisa de propósito: respostas diferentes confirmariam a existência de conexões que não são suas.
202 QR Code ainda não pronto — repita a chamada em instantes.
409 Instância já conectada (ao pedir QR Code).

Limitações e configuração

A API fala com o WhatsApp como um aparelho vinculado (não é a API oficial da Meta). Alguns recursos dependem disso ou de a Meta liberar o recurso para a conta de quem recebe. Cada rota afetada traz um aviso; o resumo:

Recurso Situação
send-text com spoiler; send-poll com allowAddOption Dependem de a Meta liberar o recurso para quem recebe. Hoje chegam como "Não foi possível carregar".
communities/join-group Não confirmado (405 no teste).
send-event-response; reminderMinutes em send-event Aceitos pelo servidor, não conferidos no aparelho.
send-status com phones Restrição de quem vê ainda não conferida no aparelho.
ask-meta-ai A conversa aparece no celular conectado e a Meta AI lembra o contexto. Use com moderação: volume alto pode chamar a atenção da Meta.
forward-message Só com MESSAGE_CONTENT_TTL_HOURS ligado no servidor.
fetch-history Ainda não funciona: o celular responde vazio.
Destrancar conversa Não existe: o WhatsApp só destranca com digital/PIN no celular.
Desconectar outro aparelho da conta; ler ou alterar o e-mail da conta Não existem: o WhatsApp só permite isso no celular principal. A instância pode desconectar a si mesma (disconnect) e listar os aparelhos (linked-devices).
Botões nativos de pedir localização, pedir endereço e lembrete Não funcionam fora da API oficial: o servidor recusa ou o aparelho não exibe.
Reportar conversão de anúncio por etiqueta Etiqueta pela API não reporta conversão à Meta. O caminho é a Conversions API com o ctwaClid (detalhes).

Teto de ritmo por conexão

O limite do WhatsApp é do número, não da conta — e estourá-lo é o caminho curto para o número ser bloqueado. Cada balde é independente e conta por conexão. A rajada inicial é um décimo do teto (mínimo 5), então passar 30 grupos seguidos não esbarra no teto.

Balde Padrão O que conta
Grupo e status 60 por minuto Mensagens endereçadas a @g.us e a status@broadcast. É o padrão de difusão, o que mais chama atenção da Meta.
Conversa 1:1 480 por minuto Conversas diretas, bem mais toleradas. O valor saiu de medição (pico real de produção: 204 msg/min e 19 msg/s), com mais que o dobro de folga — apertar aqui não atrasa envio, faz o gateway desistir e marcar deliveryError.
Operação de grupo 15 por minuto create-group, create-community, group-settings, group-participants, group-invite-link, group-review, group-mode, link-group, unlink-group. Operações estruturais são as que o WhatsApp limita com mais rigor.

Estourou o teto: HTTP 429 com o cabeçalho Retry-After dizendo quantos segundos esperar. Honre o prazo — retentar na hora só aumenta a fila.

Configurável por conexão em PUT /account/instances/{id}/settings (limiteGrupo, limiteDireto, limiteOps: -1 volta ao padrão, 0 desliga aquele balde). O teto em vigor sai no campo limites da resposta das rotas de conexão. No servidor, as variáveis são LIMITE_GRUPO_POR_MINUTO, LIMITE_DIRETO_POR_MINUTO e LIMITE_OPS_GRUPO_POR_MINUTO.

O teto protege o seu sistema de receber erro cru do WhatsApp, mas não elimina o risco de bloqueio do número. O padrão que mais derruba número é mandar a mesma mensagem para muitos grupos seguidos: espaçe os envios entre grupos em vez de disparar tudo de uma vez.

Configuração do servidor

Variável Descrição
MESSAGE_CONTENT_TTL_HOURS Guarda o conteúdo das mensagens enviadas e recebidas por esse número de horas, para forward-message encaminhar pelo ID. 0 (padrão) desliga: nada é guardado. O conteúdo vencido é apagado de hora em hora.
Preços em duas unidades: catálogo e pedidos usam milésimos (R$ 1,00 = 1000); send-charge usa centavos (R$ 1,00 = 100).

Criar instância

POST /admin/instances

Requer Authorization: Bearer <ADMIN_TOKEN>.

Campo Tipo Descrição
name string Rótulo da instância no painel.
webhookUrl string URL que recebe os eventos de entrada.
clientToken string Opcional. Se preenchido, vira obrigatório no header de toda chamada.
request
{
  "name": "Vendas",
  "webhookUrl": "https://seu-app.com/webhook",
  "clientToken": "opcional-token-de-seguranca"
}
201 created
{
  "id": "b1a2c3d4-...",
  "token": "f9e8d7c6-...",
  "clientToken": "opcional-token-de-seguranca",
  "name": "Vendas",
  "webhookUrl": "https://seu-app.com/webhook",
  "apiBaseUrl": "https://api.zapbot.online/instances/b1a2.../token/f9e8..."
}

Listar instâncias

GET /admin/instances

Retorna todas as instâncias com o status de conexão atual e o telefone conectado.

Deletar instância

DELETE /admin/instances/{id}

Desconecta o número e remove a instância. A operação não é reversível.

Status da conexão

GET /instances/{id}/token/{token}/status
200 ok
{
  "connected": true,
  "session": true,
  "smartphoneConnected": true,
  "phone": "5511777777777"
}

QR Code

GET /instances/{id}/token/{token}/qr-code/image

Inicia o pareamento e devolve o QR Code como PNG em base64. Escaneie em WhatsApp › Aparelhos conectados.

A primeira chamada apenas dispara o pareamento e pode responder 202. O código chega na chamada seguinte, poucos segundos depois.
200 ok
{
  "value": "data:image/png;base64,iVBORw0KGgoAAAANS..."
}

Variantes: GET /qr-code devolve o conteúdo bruto do código em value; GET /restart força a reconexão.

Parear por código

GET /instances/{id}/token/{token}/phone-code/{phone}

Alternativa ao QR Code: devolve um código de 8 caracteres que a pessoa digita no WhatsApp em Aparelhos conectados → Conectar com número de telefone. Serve para parear sem ter como mostrar uma imagem na tela.

{phone} vai com código do país e só dígitos. Chamar de novo durante o mesmo pareamento devolve o código em andamento em vez de começar outro. Com a instância já conectada, responde 409.

response
{
  "code": "ABCD-EFGH"
}

Reconectar

GET /instances/{id}/token/{token}/restart

Refaz a conexão com o WhatsApp sem perder a sessão — o número continua pareado. Diferente de disconnect, que desfaz o pareamento e exige ler o QR de novo.

Desconectar

GET /instances/{id}/token/{token}/disconnect

Encerra a sessão do número. A instância continua existindo e pode ser pareada de novo.

Configurar webhook

PUT /instances/{id}/token/{token}/update-every-webhooks
request
{
  "value": "https://seu-app.com/webhook",
  "notifySentByMe": true,
  "notifyGroups": true,
  "notifyGroupMessages": false,
  "notifyExtras": false
}

update-webhook-received aceita o mesmo corpo e existe por compatibilidade — nesta API as duas configuram o mesmo endereço.

notifySentByMe faz o webhook receber também o que a própria linha envia, inclusive pelo celular. Útil para manter seu banco em dia quando alguém responde pelo aparelho.

notifyGroups liga o GroupParticipantCallback (entrada, saída, remoção, promoção e rebaixamento de participante). Desligado por padrão: o volume é proporcional ao tamanho do grupo, e um grupo de mil entrando de uma vez é uma chamada com mil números.

notifyExtras é a chave mestra do conjunto de callbacks adicionais: voto de enquete, mensagem apagada, evento do contato, tipos de entrada que antes não geravam aviso (lista de contatos, localização ao vivo, produto do catálogo, vídeo redondo), etiquetas, respostas rápidas e status. Desligado por padrão — ligue só quando o seu endpoint já aguentar o volume, e tenha um plano B se o webhook cair.

notifyGroupMessages liga a mensagem escrita dentro de um grupo no webhook (o mesmo ReceivedCallback, com isGroup: true e participantPhone com quem escreveu). É separado de notifyGroups porque o volume não tem nada a ver: um é a lista de gente, o outro é o papo inteiro. Desligado por padrão.

Todos são por conexão. Desligar volta ao comportamento anterior sem precisar de deploy.

Enviar texto

POST /instances/{id}/token/{token}/send-text
Campo Tipo Descrição
phoneobrigatório string Número com código do país, só dígitos.
messageobrigatório string Texto da mensagem.
messageId string Responde citando essa mensagem.
participant string Autor da mensagem citada. Opcional: sem ele, a API usa o registro de envio/recebimento; se a mensagem não estiver no registro (grupo, enviada pelo celular ou mais antiga que o registro), o texto sai sem citação.
mentioned string[] Números mencionados; o texto deve trazer @numero.
mentionAll boolean Menciona todos do grupo; o texto deve trazer @all.
groupMentioned object[] Grupos da comunidade mencionados (phone, subject); o texto traz @ + id do grupo.
forwarded boolean Marca a mensagem como "Encaminhada".
spoiler boolean Esconde as partes entre || || até tocar; sem marcação, esconde o texto todo. Veja o aviso abaixo.
Atenção: spoiler depende de a Meta liberar o recurso para quem recebe. Hoje a mensagem chega como "Não foi possível carregar a mensagem". Use só em teste.
curl
curl -X POST "https://api.zapbot.online/instances/{id}/token/{token}/send-text" \
  -H "Content-Type: application/json" \
  -H "Client-Token: seu-token-de-seguranca" \
  -d '{"phone":"5511999999999","message":"Olá!"}'
200 ok — resposta padrão de todo send-*
{
  "zaapId": "3999984CE2CF9E0",
  "messageId": "3EB0C9A1F2D4",
  "id": "3EB0C9A1F2D4"
}

Guarde o messageId: é ele que identifica a mensagem nos webhooks de status e em send-reaction.

Enviar imagem

POST /instances/{id}/token/{token}/send-image
Campo Tipo Descrição
phoneobrigatório string Destinatário.
imageobrigatório string URL http(s) ou data URI base64 (data:image/png;base64,...).
caption string Legenda opcional.
viewOnce boolean Envia como visualização única.
forwarded boolean Marca a mensagem como "Encaminhada".
request
{
  "phone": "5511999999999",
  "image": "https://exemplo.com/foto.jpg",
  "caption": "legenda opcional"
}

Enviar áudio

POST /instances/{id}/token/{token}/send-audio

Entregue como mensagem de voz no aparelho do destinatário.

Opcionais: viewOnce (visualização única) e forwarded (marca "Encaminhada").

request
{
  "phone": "5511999999999",
  "audio": "https://exemplo.com/audio.ogg"
}

Enviar vídeo

POST /instances/{id}/token/{token}/send-video

Opcionais: viewOnce (visualização única), gif: true (toca em loop e sem som, como GIF) e forwarded (marca "Encaminhada").

request
{
  "phone": "5511999999999",
  "video": "https://exemplo.com/video.mp4",
  "caption": "legenda"
}

Enviar documento

POST /instances/{id}/token/{token}/send-document/{extensão}

A extensão vai no caminho, por exemplo /send-document/pdf.

Opcional: forwarded (marca "Encaminhada").

request
{
  "phone": "5511999999999",
  "document": "https://exemplo.com/arquivo.pdf",
  "fileName": "contrato.pdf"
}

Enviar localização

POST /instances/{id}/token/{token}/send-location

latitude e longitude são números, não strings.

request
{
  "phone": "5511999999999",
  "title": "Escritório",
  "address": "Av. Paulista, 1000",
  "latitude": -23.5615,
  "longitude": -46.656
}

Enviar contato

POST /instances/{id}/token/{token}/send-contact
request
{
  "phone": "5511999999999",
  "contactName": "João Silva",
  "contactPhone": "5511888888888",
  "contactBusinessDescription": "opcional"
}

Enviar reação

POST /instances/{id}/token/{token}/send-reaction

O messageId é o mesmo devolvido no envio ou recebido no webhook.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "reaction": "👍"
}

Enviar figurinha

POST /instances/{id}/token/{token}/send-sticker

Aceita URL ou base64. PNG e JPEG são convertidos para WebP; GIF vira figurinha animada.

request
{
  "phone": "5511999999999",
  "sticker": "https://exemplo.com/fig.webp"
}

Enviar botões

POST /instances/{id}/token/{token}/send-button-actions

Até 3 botões. type aceita REPLY, URL, CALL, COPY e CONTACT_INFO.

Com image, video ou document, a mídia vira o cabeçalho da mensagem.

WebView: num botão URL, "webview": true abre o link no navegador dentro do WhatsApp, em vez do navegador do aparelho. Só vale para type URL.

CONTACT_INFO envia o botão "Compartilhar informações de contato" do app Business (só label). A resposta do cliente chega como contato (vCard). Nos testes, o botão apareceu no celular, mas não no WhatsApp Web.

request
{
  "phone": "5511999999999",
  "message": "Como prefere continuar?",
  "footer": "Atendimento",
  "buttons": [
    {
      "type": "REPLY",
      "id": "sim",
      "label": "Quero agora"
    },
    {
      "type": "URL",
      "label": "Ver catálogo",
      "url": "https://exemplo.com",
      "webview": true
    },
    {
      "type": "CALL",
      "label": "Ligar",
      "phone": "5511999999999"
    }
  ]
}

Enviar lista de opções

POST /instances/{id}/token/{token}/send-option-list

Cabe mais opção do que em botões, e elas ficam agrupadas por seção.

request
{
  "phone": "5511999999999",
  "message": "Escolha o horário",
  "buttonText": "Ver horários",
  "sections": [
    {
      "title": "Manhã",
      "rows": [
        {
          "id": "9h",
          "title": "09:00",
          "description": "Última vaga"
        },
        {
          "id": "10h",
          "title": "10:00",
          "description": ""
        }
      ]
    }
  ]
}

Enviar botão de Pix

POST /instances/{id}/token/{token}/send-button-pix

keyType aceita CPF, CNPJ, PHONE, EMAIL e EVP.

request
{
  "phone": "5511999999999",
  "pixKey": "loja@exemplo.com",
  "keyType": "EMAIL",
  "merchantName": "Loja Exemplo"
}

Enviar enquete

POST /instances/{id}/token/{token}/send-poll

maxSelections acima de 1 permite marcar mais de uma opção. Os votos chegam pelo webhook.

Opcionais: correctAnswer (texto da opção certa: vira quiz, escolha única), endTime (encerra a votação nesse momento, em segundos Unix) e hideVoters (esconde quem votou em cada opção — só na tela dos participantes; a API continua recebendo o voto com o número).

Atenção: allowAddOption (participantes acrescentam opções) depende de a Meta liberar o recurso para quem recebe. Hoje a enquete chega como "Não foi possível carregar". Use só em teste.
request
{
  "phone": "5511999999999",
  "poll": "Qual horário fica melhor?",
  "pollOptions": [
    "Manhã",
    "Tarde",
    "Noite"
  ],
  "maxSelections": 1
}

Publicar status

POST /instances/{id}/token/{token}/send-status

Publica no status (stories). Envie message, image ou video.

Sem phones, vai para todos os contatos conforme a privacidade configurada no aparelho. Com phones, até 5000 números.

Atenção: com phones, o status é enviado só para essa lista, mas ainda não foi conferido num aparelho que quem ficou de fora realmente não vê.
request
{
  "message": "Estamos atendendo até às 20h hoje"
}

Editar mensagem

POST /instances/{id}/token/{token}/edit-message

O WhatsApp só aceita edição até cerca de 15 minutos depois do envio.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "message": "Texto corrigido"
}

Apagar mensagem

POST /instances/{id}/token/{token}/delete-message

owner identifica de quem é a mensagem quando ela não é sua; sem ele, apaga uma sua.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4"
}

Marcar como lida

POST /instances/{id}/token/{token}/read-message

Manda o recibo de leitura e zera o contador de não lidas da conversa.

request
{
  "phone": "5511999999999",
  "messageIds": [
    "3EB0C9A1F2D4"
  ]
}

Favoritar mensagem

POST /instances/{id}/token/{token}/star-message

value ausente alterna o estado atual. fromMe indica que a mensagem é sua.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "value": true
}

Listar conversas

GET /instances/{id}/token/{token}/chats

Aceita search, unread=true, archived=true|false, limit (até 200) e offset.

A lista vem do nosso banco, alimentado pelo histórico do pareamento e mantido em dia a cada mensagem — por isso responde mesmo com o número fora do ar.

Conexões pareadas antes desta versão começam vazias e vão se preenchendo conforme o tráfego.

response
{
  "chats": [
    {
      "chatId": "5511999999999@s.whatsapp.net",
      "phone": "5511999999999",
      "name": "Maria",
      "isGroup": false,
      "unread": 2,
      "archived": false,
      "pinned": false,
      "lastMessageAt": "2026-09-11T14:03:00Z"
    }
  ],
  "limit": 50,
  "offset": 0
}

Sincronizar conversas

POST /instances/{id}/token/{token}/sync-chats

Repopula a lista de chats a partir do WhatsApp, sem desparear o número. Serve para conexões pareadas antes de a listagem existir, que começam vazias.

O que ela traz: todos os grupos de que a linha participa, com nome, e toda conversa que tenha algum estado — arquivada, fixada ou silenciada.

O que ela não traz: conversas diretas comuns. O WhatsApp só registra desvios do padrão, então uma conversa direta sem arquivar, fixar nem silenciar não tem nada a sincronizar. Essas aparecem sozinhas na próxima mensagem que passar, ou de uma vez num novo pareamento.

É uma varredura completa do lado do WhatsApp: uma por conexão de cada vez, no máximo uma por minuto. Fora disso responde 409 — estado, não falha, então basta tentar mais tarde.

Arquivar conversa

POST /instances/{id}/token/{token}/archive-chat

value ausente alterna. Vale para grupo também — mande o ID do grupo em phone.

request
{
  "phone": "5511999999999",
  "value": true
}

Fixar conversa

POST /instances/{id}/token/{token}/pin-chat

O WhatsApp limita a 3 conversas fixadas.

request
{
  "phone": "5511999999999",
  "value": true
}

Silenciar conversa

POST /instances/{id}/token/{token}/mute-chat

muteSeconds define por quanto tempo. Sem ele, silencia por tempo indeterminado.

request
{
  "phone": "5511999999999",
  "value": true,
  "muteSeconds": 28800
}

Apagar conversa

POST /instances/{id}/token/{token}/delete-chat

Apaga a conversa inteira do aparelho. Não tem volta.

request
{
  "phone": "5511999999999"
}

Etiquetar conversa

POST /instances/{id}/token/{token}/label-chat

Só existe em contas WhatsApp Business. value ausente alterna.

request
{
  "phone": "5511999999999",
  "labelId": "1",
  "value": true
}

Presença (digitando…)

POST /instances/{id}/token/{token}/send-presence

status aceita composing (digitando), recording (gravando áudio), paused, available e unavailable.

Sem phone, muda a presença global da linha em vez da de uma conversa.

request
{
  "phone": "5511999999999",
  "status": "composing",
  "duration": 5
}

Perfil de um contato

POST /instances/{id}/token/{token}/contact-info

O que volta depende da privacidade da pessoa: recado oculto vem vazio, e isso não é erro.

Não responde se o número existe — para isso use phone-exists, que consulta por outra via. Esta rota devolve entrada até para número inexistente.

request
{
  "phones": [
    "5511999999999"
  ]
}
response
{
  "contacts": [
    {
      "phone": "5511999999999",
      "jid": "5511999999999@s.whatsapp.net",
      "status": "Disponível",
      "pictureId": "1258241349",
      "devices": 2
    }
  ]
}

Foto de um contato

POST /instances/{id}/token/{token}/contact-picture

preview traz a miniatura, mais rápida e suficiente para listar.

A URL é da CDN do WhatsApp e expira em horas. Quem for guardar precisa baixar os bytes.

request
{
  "phone": "5511999999999",
  "preview": true
}
response
{
  "phone": "5511999999999",
  "hasPicture": true,
  "url": "https://pps.whatsapp.net/…",
  "id": "1258241349",
  "type": "preview"
}

Estado de uma mensagem

POST /instances/{id}/token/{token}/message-status

Responde do registro desta conexão, sem falar com o WhatsApp — que não tem consulta para isso: o estado chega por recibo, uma vez, e quem não guardou perdeu.

Some junto com o registro, quando o expurgo passa.

request
{
  "messageId": "3EB0C9A1F2D4"
}
response
{
  "messageId": "3EB0C9A1F2D4",
  "direction": "out",
  "phone": "5511999999999",
  "kind": "text",
  "status": "READ",
  "at": "2026-09-11T14:03:00Z",
  "deliveredAt": "2026-09-11T14:03:01Z",
  "readAt": "2026-09-11T14:05:12Z"
}

Mídia de uma mensagem

POST /instances/{id}/token/{token}/download-media

Devolve o endereço da mídia que já re-hospedamos no recebimento. Não rebaixa do WhatsApp: o download original exige chaves que vêm dentro da mensagem, e não guardamos a mensagem.

Vale para mídia recebida, por até 24 horas. Depois disso o arquivo é apagado e a resposta é 410.

request
{
  "messageId": "3EB0C9A1F2D4"
}
response
{
  "messageId": "3EB0C9A1F2D4",
  "kind": "image",
  "url": "https://api.zapbot.online/media/8f3c….jpg"
}

Resultado da enquete

GET /instances/{id}/token/{token}/polls/{messageId}/results

Só apura enquete enviada por esta API. O voto chega com a opção em SHA-256, nunca em texto: sem ter guardado as opções no envio, o hash não endereça nada.

Opção sem voto aparece com zero, e não sumindo — o resultado mentiria por omissão.

Votar de novo substitui o voto anterior, como no aplicativo. Desmarcar tudo zera a contagem daquele eleitor.

response
{
  "messageId": "3EB0C9A1F2D4",
  "poll": "Qual horário?",
  "pollOptions": [
    "Manhã",
    "Tarde"
  ],
  "totalVoters": 2,
  "results": [
    {
      "option": "Manhã",
      "count": 2
    },
    {
      "option": "Tarde",
      "count": 0
    }
  ],
  "voters": [
    {
      "phone": "5511999999999@s.whatsapp.net",
      "options": [
        "Manhã"
      ],
      "votedAt": "2026-09-11T14:10:00Z"
    }
  ]
}

Dados de um canal

POST /instances/{id}/token/{token}/newsletter-info

Aceita newsletterId de quem já segue, ou inviteLink para espiar um canal antes de seguir.

O link pode vir inteiro ou só o código.

request
{
  "newsletterId": "120363000000000000@newsletter"
}

Publicações de um canal

POST /instances/{id}/token/{token}/newsletter-messages

Canal é difusão pública — quem segue recebe o mesmo que todo mundo — então aqui o texto vem junto.

before pagina para trás a partir de um serverId já conhecido. count vai até 100, padrão 20.

request
{
  "newsletterId": "120363000000000000@newsletter",
  "count": 20
}
response
{
  "messages": [
    {
      "messageId": "3EB0…",
      "serverId": 42,
      "type": "text",
      "text": "Novidades da semana",
      "views": 128,
      "timestamp": "2026-09-11T12:00:00Z"
    }
  ]
}

Listar contatos

GET /instances/{id}/token/{token}/contacts

Vem da agenda sincronizada pelo aparelho.

response
{
  "contacts": [
    {
      "phone": "5511999999999",
      "name": "Maria",
      "short": "Maria",
      "notify": "Maria S."
    }
  ]
}

Verificar se o número existe

POST /instances/{id}/token/{token}/phone-exists

Aceita phone (um) ou phones (vários). Vale conferir antes de disparar em lote: número inexistente conta como tentativa.

A resposta traz uma entrada para cada número enviado, inclusive os que não existem.

request
{
  "phones": [
    "5511999999999",
    "5511888888888"
  ]
}
response
{
  "exists": [
    {
      "phone": "5511999999999",
      "exists": true
    },
    {
      "phone": "5511888888888",
      "exists": false
    }
  ]
}

Bloquear contato

POST /instances/{id}/token/{token}/block

Para desbloquear, use unblock com o mesmo corpo.

request
{
  "phone": "5511999999999"
}

Listar bloqueados

GET /instances/{id}/token/{token}/blocklist
response
{
  "blocklist": [
    "5511888888888"
  ]
}

Listar grupos

GET /instances/{id}/token/{token}/groups
response
{
  "groups": [
    {
      "groupId": "120363000000000000@g.us",
      "name": "Equipe",
      "participantCount": 987,
      "amAdmin": true,
      "announce": false,
      "locked": false,
      "communityId": "120363111111111111@g.us",
      "isCommunity": false,
      "isAnnouncementGroup": false,
      "avatarUrl": "https://.../media/grupo.jpg"
    }
  ]
}

A lista vem sem os participantes — só a contagem (participantCount). Com dezenas de grupos de mil pessoas, todos os membros de todos seria uma resposta inutilizável. Para os participantes de um grupo, use groups/{groupId}.

announce restringe o envio de mensagem aos administradores; locked restringe a edição dos dados. São independentes: um grupo pode ser aberto para falar e travado para editar.

Comunidade é um grupo no protocolo, então ela aparece nesta lista igual aos filhos. isCommunity marca o grupo-pai, isAnnouncementGroup marca o grupo de avisos e communityId diz a que comunidade o grupo pertence.

Consultar um grupo

GET /instances/{id}/token/{token}/groups/{groupId}

Traz um grupo com a lista completa de participantes. groupId é o JID completo (120363000000000000@g.us); só os dígitos também são aceitos.

QueryPadrãoEfeito
participantstrue false traz só participantCount. É o jeito rápido de conferir se o grupo encheu, sem carregar mil números.
inviteLinktrue false pula a consulta ao WhatsApp e não traz inviteLink.
response
{
  "groupId": "120363000000000000@g.us",
  "name": "AULÃO #01",
  "description": "...",
  "participantCount": 987,
  "amAdmin": true,
  "announce": false,
  "locked": false,
  "communityId": "120363111111111111@g.us",
  "isAnnouncementGroup": false,
  "inviteLink": "https://chat.whatsapp.com/XXXXXXXX",
  "participants": [
    {
      "phone": "5511999999999",
      "name": "Maria",
      "isAdmin": false,
      "isSuperAdmin": false,
      "joinedAt": 1759320000000
    }
  ]
}

participants[].phone já vem resolvido de LID para número quando possível: um @lid não endereça ninguém nem cruza com a agenda. inviteLink só vem quando amAdmin é true — link de convite exige ser administrador.

participants[].joinedAt é quando nós vimos a pessoa entrar (milissegundos Unix). O WhatsApp não informa data de entrada em lugar nenhum, então o campo só existe para quem entrou enquanto a conexão estava no ar — fica ausente para quem já estava no grupo, porque inventar data seria pior que não ter. Quem sai perde a data de propósito: se voltar, a data é a da volta.

A foto de um grupo sozinho: GET /instances/{id}/token/{token}/groups/{groupId}/avatar.

Criar grupo

POST /instances/{id}/token/{token}/create-group

Quem cria entra como administrador. Números que não existem no WhatsApp são recusados.

request
{
  "name": "Time de vendas",
  "phones": [
    "5511999999999",
    "5511888888888"
  ]
}

Participantes do grupo

POST /instances/{id}/token/{token}/group-participants

action aceita add, remove, promote e demote.

A resposta diz o resultado de cada número: quem tem a privacidade fechada não entra por adição, só por convite.

request
{
  "groupId": "120363000000000000@g.us",
  "phones": [
    "5511888888888"
  ],
  "action": "add"
}

Configurar grupo

PUT /instances/{id}/token/{token}/group-settings

Só muda o que você mandar. O nome tem limite de 25 caracteres.

announce restringe o envio aos administradores; locked restringe a edição dos dados do grupo.

request
{
  "groupId": "120363000000000000@g.us",
  "name": "Time de vendas",
  "description": "Combinados do time",
  "announce": false
}

Trocar a foto do grupo

PUT /instances/{id}/token/{token}/group-picture

Troca a foto de um grupo ou de uma comunidade — é a mesma chamada, passando o JID da comunidade em groupId (a comunidade é um grupo "pai" no protocolo).

request
{
  "groupId": "120363000000000000@g.us",
  "image": "https://exemplo.com/capa.jpg"
}

image aceita URL http(s) pública ou data URI base64, igual ao send-image. A imagem é normalizada para JPEG quadrado antes de subir: o WhatsApp recusa o que não for.

Existe como rota própria, e não só como campo do group-settings, por paridade de nome com a Z-API. Trocar a foto de um número é outra coisa: use PUT profile-picture. Resposta: {"value": true}.

Espiar grupo pelo link

POST /instances/{id}/token/{token}/group-preview

Mostra nome, descrição e número de participantes sem entrar no grupo.

request
{
  "inviteLink": "https://chat.whatsapp.com/XXXXXXXX"
}

Entrar em grupo

POST /instances/{id}/token/{token}/join-group
request
{
  "inviteLink": "https://chat.whatsapp.com/XXXXXXXX"
}

Sair do grupo

POST /instances/{id}/token/{token}/leave-group
request
{
  "groupId": "120363000000000000@g.us"
}

Modo de entrada do grupo

PUT /instances/{id}/token/{token}/group-mode

requireApproval põe quem entra pelo link numa fila de aprovação. adminsOnlyAdd restringe quem pode adicionar.

request
{
  "groupId": "120363000000000000@g.us",
  "requireApproval": true
}

Aprovar ou recusar entrada

POST /instances/{id}/token/{token}/group-review

action aceita approve ou reject.

request
{
  "groupId": "120363000000000000@g.us",
  "phones": [
    "5511888888888"
  ],
  "action": "approve"
}

Listar canais

GET /instances/{id}/token/{token}/newsletters
response
{
  "newsletters": [
    {
      "newsletterId": "120363000000000000@newsletter",
      "name": "Avisos",
      "subscribers": 128
    }
  ]
}

Criar canal

POST /instances/{id}/token/{token}/create-newsletter
request
{
  "name": "Avisos da loja",
  "description": "Promoções e horários"
}

Seguir canal

POST /instances/{id}/token/{token}/follow-newsletter

value ausente alterna entre seguir e deixar de seguir.

mute-newsletter tem o mesmo corpo e silencia o canal.

request
{
  "newsletterId": "120363000000000000@newsletter",
  "value": true
}

Listar etiquetas

GET /instances/{id}/token/{token}/labels

Só existe em contas WhatsApp Business.

A lista é montada a partir dos eventos que o WhatsApp manda ao sincronizar — o protocolo não tem uma consulta que devolva tudo de uma vez.

Para cor em hex, conversas por etiqueta e sincronização completa, veja labels/full e labels/sync.

response
{
  "labels": [
    {
      "labelId": "1",
      "name": "Novo cliente",
      "color": 3
    }
  ]
}

Criar ou renomear etiqueta

PUT /instances/{id}/token/{token}/labels

color é o índice da paleta do WhatsApp, de 0 a 19 — não é uma cor livre.

Sem labelId, cria uma nova.

request
{
  "labelId": "1",
  "name": "Cliente fiel",
  "color": 5
}

Recusar chamada

POST /instances/{id}/token/{token}/reject-call

O callId chega pelo webhook quando a chamada entra.

request
{
  "phone": "5511999999999",
  "callId": "ABC123"
}

Enviar álbum

POST /instances/{id}/token/{token}/send-album

Envia várias fotos e vídeos agrupados num álbum (mosaico), como o celular faz.

De 2 a 30 itens; cada item leva image ou video (URL ou base64). A legenda exibida no mosaico é a do primeiro item.

Todas as mídias sobem antes do envio: se uma falhar, nada é enviado. A resposta traz o ID da mensagem do álbum. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "album": [
    {
      "image": "https://exemplo.com/foto1.jpg",
      "caption": "Coleção nova"
    },
    {
      "video": "https://exemplo.com/video.mp4"
    },
    {
      "image": "https://exemplo.com/foto2.jpg"
    }
  ]
}

Enviar vídeo redondo

POST /instances/{id}/token/{token}/send-ptv

Envia vídeo redondo (a "bolinha" de vídeo gravado na hora).

O aparelho recorta em círculo: prefira vídeo quadrado (ex.: 480x480). Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "video": "https://exemplo.com/video-quadrado.mp4"
}

Enviar vários contatos

POST /instances/{id}/token/{token}/send-contacts

Envia vários contatos numa mensagem só.

Mínimo de 2; para um contato, use send-contact. name e phone são obrigatórios em cada item. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "contacts": [
    {
      "name": "Maria Silva",
      "phone": "5511988888888"
    },
    {
      "name": "João Souza",
      "phone": "5511977777777"
    }
  ]
}

Enviar localização em tempo real

POST /instances/{id}/token/{token}/send-live-location

Envia um cartão de localização em tempo real.

É um ponto só: aparece como localização em tempo real, mas não há atualização contínua. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "latitude": -23.5505,
  "longitude": -46.6333,
  "caption": "Estou aqui"
}

Enviar convite de grupo

POST /instances/{id}/token/{token}/send-group-invite

Envia o cartão de convite de grupo, com o botão "Entrar no grupo".

Usa o código do link de convite do grupo: a instância precisa ser admin. O convite expira em 3 dias. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "groupId": "120363000000000000@g.us",
  "message": "Entre no nosso grupo de clientes"
}

Pedir número do contato

POST /instances/{id}/token/{token}/request-phone-number

Pede ao contato que compartilhe o próprio número. Útil para contatos que chegam só com LID.

O que chega no webhook quando o contato compartilha ainda não foi conferido. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999"
}

Compartilhar meu número

POST /instances/{id}/token/{token}/share-phone-number

Envia o seu número ao contato que só vê você por LID. É o par de request-phone-number. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999"
}

Fixar mensagem

POST /instances/{id}/token/{token}/pin-message

Fixa (ou desafixa) uma mensagem no topo da conversa.

duration: 24h, 7d (padrão, como no aparelho) ou 30d. fromMe diz se a mensagem é sua. unpin: true desafixa (desafixar ainda não foi conferido em aparelho). Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "fromMe": true,
  "duration": "7d"
}

Manter mensagem temporária

POST /instances/{id}/token/{token}/keep-message

Numa conversa com mensagens temporárias, marca a mensagem para não sumir (aparece com o ícone de mantida).

undo: true desfaz. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "fromMe": false
}

Encaminhar pelo ID

POST /instances/{id}/token/{token}/forward-message

Reenvia uma mensagem já enviada ou recebida, pelo ID, com a marca "Encaminhada".

phone é o destino. messagePhone (conversa de origem) é aceito por compatibilidade, mas o ID basta.

Mídia não é baixada de novo: a cópia aponta para o mesmo arquivo nos servidores do WhatsApp, como o próprio app faz. Resposta: a padrão de todo send-* (zaapId, messageId, id).

Atenção: só funciona com MESSAGE_CONTENT_TTL_HOURS ligado no servidor. Com a guarda desligada (padrão, 0), a rota responde erro. Ligada, só dá para encaminhar mensagens enviadas ou recebidas dentro desse prazo (contado a partir de quando a guarda foi ligada).
request
{
  "phone": "5511988888888",
  "messageId": "3EB0C9A1F2D4"
}

Marcar áudio como ouvido

POST /instances/{id}/token/{token}/mark-played

Marca um áudio recebido como ouvido (microfone azul para quem mandou), sem ninguém tocar.

participant: autor do áudio, quando é de grupo.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4"
}
response
{
  "value": true
}

Enviar evento

POST /instances/{id}/token/{token}/send-event

Envia um convite de evento com confirmação de presença (vou / não vou / talvez).

Horários em segundos Unix. name e startTime são obrigatórios; endTime, description, location e extraGuestsAllowed são opcionais.

scheduleCall: true transforma o evento em chamada agendada; com joinLink (gerado em call-link com startTime), aparece o botão "Entrar na ligação".

As confirmações chegam no webhook EventResponseCallback. Resposta: a padrão de todo send-* (zaapId, messageId, id).

Atenção: reminderMinutes (aviso X minutos antes) é aceito pelo servidor, mas a exibição no aparelho ainda não foi conferida.
request
{
  "phone": "5511999999999",
  "name": "Reunião de alinhamento",
  "description": "Pauta: metas do mês",
  "startTime": 1791205200,
  "endTime": 1791208800,
  "location": {
    "latitude": -23.5505,
    "longitude": -46.6333,
    "name": "Escritório"
  },
  "extraGuestsAllowed": false,
  "reminderMinutes": 30
}

Editar ou cancelar evento

POST /instances/{id}/token/{token}/send-edit-event

Edita ou cancela um evento enviado por esta instância. canceled: true cancela.

event.name e event.dateTime são obrigatórios; a edição substitui os dados do evento. dateTime no formato 2026-10-05T16:00:00, no horário de Brasília. Em location, só o name é usado.

Só funciona com eventos enviados por esta instância (é preciso o segredo do evento). Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "eventMessageId": "3EB0C9A1F2D4",
  "event": {
    "name": "Reunião de alinhamento",
    "dateTime": "2026-10-05T16:00:00",
    "description": "Novo horário",
    "location": {
      "name": "Sala 2"
    },
    "canceled": false
  }
}

Responder a um evento

POST /instances/{id}/token/{token}/send-event-response

Responde a um evento criado por outra pessoa. response: GOING, NOT_GOING ou MAYBE.

Em grupo, informe participant (quem criou o evento). O evento precisa ter chegado nesta instância. Resposta: a padrão de todo send-* (zaapId, messageId, id).

Atenção: aceito pelo servidor, mas ainda não conferido no aparelho.
request
{
  "phone": "5511999999999",
  "eventMessageId": "3EB0C9A1F2D4",
  "response": "GOING",
  "extraGuests": 1
}

Votar em enquete

POST /instances/{id}/token/{token}/send-poll-vote

Vota numa enquete da conversa. pollVote[].name é o texto exato da opção; mais de um item vale para enquete de múltipla escolha.

pollSender: autor da enquete, em grupo (opcional). Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "pollMessageId": "3EB0C9A1F2D4",
  "pollVote": [
    {
      "name": "Tarde"
    }
  ]
}

Marcar como não lida

POST /instances/{id}/token/{token}/mark-unread

Marca a conversa como não lida (a bolinha verde no celular). value: false desfaz, marcando como lida.

Funciona com a conversa fechada no celular; se ela estiver aberta, o celular marca como lida na hora.

request
{
  "phone": "5511999999999",
  "value": true
}
response
{
  "value": true
}

Mensagens temporárias da conversa

POST /instances/{id}/token/{token}/chat-disappearing

Liga ou desliga mensagens temporárias numa conversa ou grupo.

seconds: 0 (desliga), 86400 (24 horas), 604800 (7 dias) ou 7776000 (90 dias).

request
{
  "phone": "5511999999999",
  "seconds": 604800
}
response
{
  "value": true
}

Limpar conversa

POST /instances/{id}/token/{token}/clear-chat

Apaga as mensagens da conversa sem apagar a conversa. keepStarred: true preserva as favoritadas.

request
{
  "phone": "5511999999999",
  "keepStarred": true
}
response
{
  "value": true
}

Apagar só para mim

POST /instances/{id}/token/{token}/delete-for-me

Apaga uma mensagem só deste número (e dos aparelhos vinculados); o contato continua vendo.

Em grupo, mensagem de outra pessoa exige participant (o autor). A API usa o LID da conversa quando o conhece: indexada pelo número, o aparelho ignorava o pedido.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "fromMe": true
}
response
{
  "value": true
}

Trancar conversa

POST /instances/{id}/token/{token}/lock-chat

Tranca a conversa: no celular ela vai para "Conversas trancadas".

Atenção: destrancar não é possível pela API. O WhatsApp só destranca com digital/PIN no celular; value: false é aceito e ignorado.
request
{
  "phone": "5511999999999",
  "value": true
}
response
{
  "value": true
}

Salvar contato

POST /instances/{id}/token/{token}/save-contact

Grava ou renomeia o contato na agenda sincronizada do número.

firstName padrão: a primeira palavra de name. saveOnPhone: true também grava na agenda do celular.

request
{
  "phone": "5511999999999",
  "name": "Maria Silva",
  "saveOnPhone": true
}
response
{
  "value": true
}

Acompanhar presença

POST /instances/{id}/token/{token}/subscribe-presence

Passa a receber online, digitando e visto por último do contato, no webhook PresenceChatCallback.

O WhatsApp só manda presença enquanto a conta está disponível (online), e o visto por último respeita a privacidade do contato. Nos testes chegaram UNAVAILABLE, COMPOSING e PAUSED; RECORDING não chegou.

Atenção: deixar a conta online pela API pode fazer o celular parar de mostrar notificações.
request
{
  "phone": "5511999999999"
}
response
{
  "value": true
}

Recuperar "Aguardando mensagem"

POST /instances/{id}/token/{token}/request-unavailable-message

Pede ao celular a cópia de uma mensagem que este aparelho não conseguiu decifrar ("Aguardando mensagem"). A cópia chega depois, pelo webhook, como mensagem normal.

Use phone ou groupId; em grupo, participant é o autor. O celular principal precisa estar online.

request
{
  "groupId": "120363000000000000@g.us",
  "messageId": "3EB0C9A1F2D4",
  "participant": "5511999999999"
}
response
{
  "value": true
}

Privacidade avançada da conversa

POST /instances/{id}/token/{token}/limit-sharing

Liga ou desliga a privacidade avançada da conversa. phone aceita número ou grupo.

Em conversa individual a resposta é a padrão de envio; em grupo, {"value": true}.

Atenção: em grupo, implementado mas ainda não conferido no aparelho (no individual, funcionou).
request
{
  "phone": "5511999999999",
  "value": true
}

Nota da conversa

POST /instances/{id}/token/{token}/chat-note

Cria, edita ou apaga a nota da conversa (app Business); aparece no perfil do contato.

Sem id, cria; com id, edita; delete: true apaga (exige id).

request
{
  "phone": "5511999999999",
  "text": "Prefere contato à tarde"
}
response
{
  "id": "b1a2c3d4-0000-0000-0000-000000000000"
}

Resposta rápida

POST /instances/{id}/token/{token}/quick-reply

Cria, edita ou apaga uma resposta rápida do app Business. shortcut é o atalho (sem a barra) e message o texto.

Sem id, cria; com id, edita; delete: true apaga (exige id).

Atenção: nos testes, a resposta rápida só apareceu no celular com id numérico (o app usa 1, 2, 3…). Sem id, a API gera um ID hexadecimal que o celular ignorou. Informe um id numérico livre.
request
{
  "id": "12",
  "shortcut": "horario",
  "message": "Atendemos de segunda a sexta, das 8h às 18h.",
  "keywords": [
    "horário",
    "funcionamento"
  ]
}
response
{
  "id": "12"
}

Histórico sob demanda

POST /instances/{id}/token/{token}/fetch-history

Pede ao celular as mensagens anteriores a uma mensagem conhecida e devolve no formato do webhook de mensagem recebida.

Use phone ou groupId. messageId, timestamp (segundos) e fromMe são da mensagem mais antiga que você conhece; count até 50. O celular precisa estar online (espera até 45 s).

Atenção: ainda não funciona: nos testes, o celular respondeu vazio.
request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "timestamp": 1790000000,
  "fromMe": false,
  "count": 50
}
response
{
  "messages": [
    {
      /* ReceivedCallback, um por mensagem */
    }
  ]
}

Número e LID

POST /instances/{id}/token/{token}/resolve-lid

Converte número em LID e LID em número (envie o LID em phone para o sentido inverso).

request
{
  "phone": "5511999999999"
}
response
{
  "phone": "5511999999999",
  "lid": "123456789012345@lid"
}

Achar contato por @username

POST /instances/{id}/token/{token}/resolve-username

Acha o contato dono de um @username do WhatsApp (3 a 35 caracteres). Username protegido exige o pin de 4 dígitos; sem ele, a resposta vem com pinRequired: true.

Atenção: a consulta foi validada com username inexistente; ainda falta testar com um username real.
request
{
  "username": "lojaexemplo"
}
response
{
  "phone": "5511999999999",
  "lid": "123456789012345@lid",
  "username": "lojaexemplo"
}

Limites da conta

GET /instances/{id}/token/{token}/account-limits

Consulta a cota de conversas novas do ciclo e se há bloqueio temporário para iniciar conversas.

total_quota: -1 = sem limite. Se uma das consultas falhar, o erro vem em cappingError ou timelockError.

response
{
  "newChats": {
    "total_quota": -1,
    "used_quota": 0,
    "cycle_start_timestamp": "1790000000",
    "cycle_end_timestamp": "1792592000",
    "capping_status": "NONE",
    "ote_status": "...",
    "mv_status": "..."
  },
  "timelock": {
    "is_active": false,
    "time_enforcement_ends": "",
    "enforcement_type": ""
  }
}

Aparelhos vinculados

GET /instances/{id}/token/{token}/linked-devices

Lista os aparelhos da conta. device: 0 é o celular principal; thisInstance marca o aparelho desta instância.

Atenção: só lista. Desconectar outro aparelho não existe: o WhatsApp só permite isso no celular principal.
response
[
  {
    "jid": "5511999999999@s.whatsapp.net",
    "device": 0,
    "primary": true,
    "thisInstance": false
  },
  {
    "jid": "5511999999999:12@s.whatsapp.net",
    "device": 12,
    "primary": false,
    "thisInstance": true
  }
]

Listar etiquetas completas

GET /instances/{id}/token/{token}/labels/full

Lista as etiquetas com cor em hex, ordem, tipo, se está ativa e quantas conversas têm cada uma.

Sincroniza com o celular nos dois sentidos: o que é feito no app chega aqui em cerca de 1 s. Etiquetas padrão do app trazem predefinedId e predefined.

response
[
  {
    "labelId": "11",
    "name": "Cliente fiel",
    "color": 5,
    "hexColor": "#55CCB3",
    "orderIndex": 10,
    "type": "CUSTOM",
    "active": true,
    "chats": 3
  }
]

Criar etiqueta

POST /instances/{id}/token/{token}/labels

Cria uma etiqueta como o app cria: próximo ID livre (as próprias começam em 11; 1 a 10 são reservados às padrão), tipo CUSTOM, ativa e no fim da lista.

color: índice da paleta, de 0 a 19 (veja labels/colors). name até 100 caracteres.

Com predefined, ativa uma sugestão padrão do app com o nome e a cor dela (idempotente): NEW_CUSTOMER, NEW_ORDER, PENDING_PAYMENT, PAID, ORDER_COMPLETE, IMPORTANT, FOLLOW_UP, LEAD.

Atenção: etiqueta aplicada pela API não reporta conversão de anúncio à Meta. Veja Origem do anúncio e conversões.
request
{
  "name": "Cliente fiel",
  "color": 5
}
response
{
  "labelId": "11"
}

Conversas com a etiqueta

GET /instances/{id}/token/{token}/labels/{labelId}/chats

Lista as conversas que têm a etiqueta.

response
[
  {
    "phone": "5511999999999",
    "chatJid": "123456789012345@lid",
    "labelId": "11"
  }
]

Etiquetas da conversa

GET /instances/{id}/token/{token}/chats/{phone}/labels

Lista as etiquetas aplicadas a uma conversa, no mesmo formato de labels/full.

response
[
  {
    "labelId": "11",
    "name": "Cliente fiel",
    "color": 5,
    "hexColor": "#55CCB3",
    "orderIndex": 10,
    "type": "CUSTOM",
    "active": true,
    "chats": 3
  }
]

Sincronizar etiquetas

POST /instances/{id}/token/{token}/labels/sync

Puxa do celular todas as etiquetas e associações (sincronização completa) e devolve a lista no formato de labels/full. Sem corpo.

response
[
  {
    "labelId": "11",
    "name": "Cliente fiel",
    "color": 5,
    "hexColor": "#55CCB3",
    "orderIndex": 10,
    "type": "CUSTOM",
    "active": true,
    "chats": 3
  }
]

Etiqueta em mensagem

POST /instances/{id}/token/{token}/label-message

Aplica ou remove etiqueta de uma mensagem. value: false remove; fromMe diz se a mensagem é sua.

Atenção: aceito pelo servidor; a exibição no app ainda não foi conferida.
request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4",
  "fromMe": false,
  "labelId": "11",
  "value": true
}
response
{
  "value": true
}

Paleta de cores

GET /instances/{id}/token/{token}/labels/colors

Devolve a paleta de 20 cores do WhatsApp, do índice para o hex. É a mesma resposta de business/tags/colors.

response
{
  "0": "#FF9485",
  "1": "#64C4FF",
  "2": "#FFD429",
  /* ... até "19" */
}

Sugestões de etiqueta

GET /instances/{id}/token/{token}/labels/suggestions

Lista as etiquetas padrão do app Business ainda não ativadas (as "Sugestões"). Ative com POST labels e predefined.

response
[
  {
    "labelId": "1",
    "name": "Novo cliente",
    "color": 1,
    "hexColor": "#64C4FF",
    "orderIndex": 0,
    "active": false,
    "chats": 0,
    "predefinedId": 1,
    "predefined": "NEW_CUSTOMER"
  }
]

Listar tags

GET /instances/{id}/token/{token}/tags

Lista as etiquetas no formato tags (id, nome e índice de cor).

response
[
  {
    "id": "11",
    "name": "Cliente fiel",
    "color": 5
  }
]

Criar tag

POST /instances/{id}/token/{token}/business/create-tag

Cria uma etiqueta. Mesmo corpo e regras de POST labels (aceita predefined).

request
{
  "name": "Cliente fiel",
  "color": 5
}
response
{
  "id": "11"
}

Editar tag

POST /instances/{id}/token/{token}/business/edit-tag/{tagId}

Renomeia ou troca a cor de uma etiqueta. Sem color, mantém a cor atual.

request
{
  "name": "Cliente VIP",
  "color": 9
}
response
{
  "success": true
}

Apagar tag

DELETE /instances/{id}/token/{token}/business/tag/{tagId}

Apaga uma etiqueta.

response
{
  "success": true
}

Cores das tags

GET /instances/{id}/token/{token}/business/tags/colors

Paleta de cores, do índice para o hex. Igual a labels/colors.

response
{
  "0": "#FF9485",
  "1": "#64C4FF",
  /* ... até "19" */
}

Aplicar tag na conversa

PUT /instances/{id}/token/{token}/chats/{phone}/tags/{tag}/add

Aplica a etiqueta {tag} (o ID) à conversa. Sem corpo. A mudança chega ao celular pela sincronização.

Atenção: etiqueta aplicada pela API não reporta conversão de anúncio à Meta. Veja Origem do anúncio e conversões.
response
{
  "value": true
}

Remover tag da conversa

PUT /instances/{id}/token/{token}/chats/{phone}/tags/{tag}/remove

Remove a etiqueta {tag} da conversa. Sem corpo.

response
{
  "value": true
}

Ler catálogo

POST /instances/{id}/token/{token}/catalog

Lê o catálogo de uma empresa: sem phone, o próprio. limit de 1 a 100 (padrão 50); para a página seguinte, envie em after o cursor paging.cursors.after.

Devolve a resposta da GraphQL da Meta como veio. Preços em milésimos (R$ 1,00 = "1000").

request
{
  "limit": 50
}
response — trecho
{
  "data": {
    "xfb_whatsapp_catalog": {
      "product_catalog": {
        "products": [
          {
            "id": "1234567890123456",
            "retailer_id": "SKU-1",
            "name": "Mesa",
            "description": "Mesa de madeira",
            "price": "990000",
            "currency": "BRL",
            "sale_price": {
              "price": "500000"
            },
            "media": {
              "images": [
                {
                  "original_image_url": "https://..."
                }
              ]
            }
          }
        ],
        "paging": {
          "cursors": {
            "after": "..."
          }
        }
      }
    }
  }
}

Enviar produto

POST /instances/{id}/token/{token}/send-product

Envia um produto do próprio catálogo como cartão (foto, preço, promoção e botão Ver). productId é o id do produto em catalog. Resposta: a padrão de todo send-* (zaapId, messageId, id).

Atenção: o produto precisa estar aprovado pela Meta. Em análise, o cartão chega, mas ao abrir mostra "item foi removido".
request
{
  "phone": "5511999999999",
  "productId": "1234567890123456",
  "message": "Olha essa",
  "footer": "Loja Exemplo"
}

Criar produto

POST /instances/{id}/token/{token}/catalog/product

Cria um produto no catálogo. name e ao menos uma imagem são obrigatórios.

Preços em milésimos, como texto (R$ 990,00 = "990000"). images: até 10, URLs ou base64 (JPEG ou PNG, até 16 MB cada). hidden: true cria oculto.

Devolve a resposta da GraphQL da Meta como veio.

request
{
  "name": "Mesa",
  "description": "Mesa de madeira",
  "price": "990000",
  "salePrice": "500000",
  "currency": "BRL",
  "retailerId": "SKU-1",
  "hidden": false,
  "images": [
    "https://exemplo.com/mesa.jpg"
  ]
}

Editar produto

PUT /instances/{id}/token/{token}/catalog/product

Edita um produto (productId obrigatório). Mesmos campos de criar.

Devolve a resposta da GraphQL da Meta como veio.

Atenção: substitui o produto inteiro: campo não enviado (ex.: salePrice) some. Fotos já hospedadas pelo WhatsApp podem ser reenviadas pela URL do CDN, sem novo upload.
request
{
  "productId": "1234567890123456",
  "name": "Mesa",
  "description": "Mesa de madeira",
  "price": "1050000",
  "salePrice": "500000",
  "currency": "BRL",
  "retailerId": "SKU-1",
  "hidden": false,
  "images": [
    "https://exemplo.com/mesa.jpg"
  ]
}

Apagar produtos

POST /instances/{id}/token/{token}/catalog/delete-products

Apaga produtos pelo ID. Devolve a resposta da GraphQL da Meta como veio (com a quantidade apagada).

request
{
  "productIds": [
    "1234567890123456"
  ]
}

Ligar carrinho

POST /instances/{id}/token/{token}/catalog/cart

Liga ou desliga o carrinho do catálogo. Com ele ligado, o cliente monta o pedido e ele chega no webhook order.

Devolve a resposta da GraphQL da Meta como veio.

request
{
  "enabled": true
}

Itens do pedido

POST /instances/{id}/token/{token}/order-details

Busca os itens de um pedido recebido: produtos, quantidade, preço, subtotal, total e foto.

orderId e token vêm do webhook order.

request
{
  "orderId": "1234567890123456",
  "token": "AR4b...token-do-webhook"
}

Enviar cobrança

POST /instances/{id}/token/{token}/send-charge

Envia uma cobrança ("Pedido") do app Business: o cliente revisa e confirma.

Valores em centavos (R$ 500,00 = 50000), diferente do catálogo. name e amount são obrigatórios em cada item; quantity padrão 1; currency padrão BRL; image vira o cabeçalho.

orderRequestId liga a cobrança ao pedido que o cliente mandou do carrinho. Informe seu próprio referenceId para atualizar depois com charge-status: sem ele, a API gera um que não volta na resposta (só no webhook de confirmação).

A confirmação do cliente chega no webhook paymentConfirmation. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "message": "Seu pedido",
  "image": "https://exemplo.com/mesa.jpg",
  "currency": "BRL",
  "orderRequestId": "1234567890123456",
  "referenceId": "PED-1001",
  "items": [
    {
      "productId": "1234567890123456",
      "name": "Mesa",
      "amount": 50000,
      "quantity": 1
    }
  ]
}

Atualizar cobrança

POST /instances/{id}/token/{token}/charge-status

Atualiza uma cobrança, como o "Atualizar status" do app. referenceId é obrigatório.

status: pending, payment_requested, preparing_to_ship, partially_shipped, shipped, delivered, completed ou canceled; message vai como recado. paid (opcional) marca pago ou não pago.

Resposta: a padrão de envio, da última mensagem enviada.

request
{
  "phone": "5511999999999",
  "referenceId": "PED-1001",
  "status": "shipped",
  "message": "Saiu para entrega",
  "paid": true
}

Enviar formulário

POST /instances/{id}/token/{token}/send-form

Envia o formulário do app Business (modelo da Meta). title é obrigatório.

Os campos booleanos escolhem o que pedir: fullName, email, phoneNumber, deliveryAddress, cpfOrCnpj, document. customFields acrescenta campos de texto (label obrigatório).

As respostas chegam no webhook formResponse. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "phone": "5511999999999",
  "message": "Preencha para receber a proposta",
  "title": "Proposta",
  "description": "Leva 1 minuto",
  "fullName": true,
  "email": true,
  "phoneNumber": true,
  "customFields": [
    {
      "label": "Empresa",
      "helper": "Nome fantasia"
    }
  ]
}

Dados do cliente e etapa do lead

POST /instances/{id}/token/{token}/customer-data

Grava os dados do cliente e a etapa do lead (Customer Manager do app Business).

leadStage: LEAD, INTAKE, QUALIFIED, CONVERTED ou LOST. contactType: NONE, CUSTOMER, COLLEAGUE, VENDOR ou PERSONAL. Também email, altPhones, address, birthday, acquisitionSource e lastOrder.

Exige o LID do contato (a API procura sozinha).

Atenção: o servidor grava, mas a etapa não apareceu no app da conta de teste (recurso provavelmente não liberado). O formato de birthday, lastOrder e acquisitionSource não foi confirmado.
request
{
  "phone": "5511999999999",
  "leadStage": "QUALIFIED",
  "contactType": "CUSTOMER",
  "email": "cliente@exemplo.com"
}
response
{
  "value": true
}

Ler perfil comercial

GET /instances/{id}/token/{token}/business-profile

Devolve endereço, e-mail, descrição e sites do perfil comercial do próprio número, e a capa atual (id para apagar, url para ver).

response
{
  "address": "Rua Exemplo, 100",
  "email": "contato@exemplo.com",
  "description": "Móveis sob medida",
  "websites": [
    "https://exemplo.com"
  ],
  "coverPhoto": {
    "id": "1234567890123456",
    "url": "https://..."
  }
}

Alterar perfil comercial

POST /instances/{id}/token/{token}/business-profile

Altera só os campos enviados; os ausentes ficam como estão. description até 1024 bytes; no máximo 2 sites.

request
{
  "description": "Móveis sob medida desde 2010",
  "websites": [
    "https://exemplo.com"
  ]
}
response
{
  "value": true
}

Trocar capa

PUT /instances/{id}/token/{token}/business/cover-photo

Troca a foto de capa do perfil comercial. value: URL ou base64 de uma imagem JPEG. Devolve o id da capa nova.

request
{
  "value": "https://exemplo.com/capa.jpg"
}
response
{
  "id": "1234567890123456"
}

Apagar capa

DELETE /instances/{id}/token/{token}/business/cover-photo/{coverId}

Apaga a capa pelo id (o de business-profile ou o devolvido ao trocar).

response
{
  "value": true
}

Nome do perfil

PUT /instances/{id}/token/{token}/profile-name

Troca o nome do perfil do número.

request
{
  "value": "Loja Exemplo"
}
response
{
  "value": true
}

Recado do perfil

PUT /instances/{id}/token/{token}/profile-description

Troca o recado ("Sobre") do perfil. O servidor grava e devolve na leitura; o app Business da conta de teste não mostrava o campo.

request
{
  "value": "Atendimento das 8h às 18h"
}
response
{
  "value": true
}

Foto do perfil

PUT /instances/{id}/token/{token}/profile-picture

Troca a foto do perfil. value: URL ou base64. value vazio ("") apaga a foto.

request
{
  "value": "https://exemplo.com/logo.jpg"
}
response
{
  "value": true
}

Ler privacidade

GET /instances/{id}/token/{token}/privacy

Lê as configurações de privacidade direto do servidor, sem cache. Valores como o WhatsApp devolve: all, contacts, contact_blacklist, none, match_last_seen.

response
{
  "lastSeen": "all",
  "online": "all",
  "photoVisualization": "contacts",
  "description": "all",
  "groupAdd": "contacts",
  "readReceipts": "all",
  "callAdd": "all",
  "messages": "all"
}

Privacidade: visto por último

POST /instances/{id}/token/{token}/privacy/last-seen

Define quem vê o visto por último. Corpo: visualizationType (ou type) com ALL, CONTACTS, CONTACT_BLACKLIST (meus contatos, exceto…) ou NONE.

request
{
  "visualizationType": "CONTACTS"
}
response
{
  "success": true
}

Privacidade: online

POST /instances/{id}/token/{token}/privacy/online

Define quem vê quando você está online: ALL ou SAME_LAST_SEEN (igual ao visto por último).

request
{
  "visualizationType": "SAME_LAST_SEEN"
}
response
{
  "success": true
}

Privacidade: foto do perfil

POST /instances/{id}/token/{token}/privacy/photo-visualization

Define quem vê a foto do perfil. Corpo: visualizationType (ou type) com ALL, CONTACTS, CONTACT_BLACKLIST (meus contatos, exceto…) ou NONE.

request
{
  "visualizationType": "CONTACTS"
}
response
{
  "success": true
}

Privacidade: recado

POST /instances/{id}/token/{token}/privacy/description

Define quem vê o recado. Corpo: visualizationType (ou type) com ALL, CONTACTS, CONTACT_BLACKLIST (meus contatos, exceto…) ou NONE.

request
{
  "visualizationType": "ALL"
}
response
{
  "success": true
}

Privacidade: adicionar a grupos

POST /instances/{id}/token/{token}/privacy/group-add

Define quem pode adicionar o número a grupos. Corpo: visualizationType (ou type) com ALL, CONTACTS, CONTACT_BLACKLIST (meus contatos, exceto…) ou NONE.

request
{
  "visualizationType": "CONTACTS"
}
response
{
  "success": true
}

Privacidade: confirmação de leitura

POST /instances/{id}/token/{token}/privacy/read-receipts

Liga ou desliga a confirmação de leitura. Sem corpo: o valor vai na query, ?value=enable ou ?value=disable.

response
{
  "success": true
}

Privacidade: temporárias padrão

POST /instances/{id}/token/{token}/privacy/messages-duration

Define a duração padrão das mensagens temporárias para conversas novas. Sem corpo: ?value=days90, days7, hours24 ou disable. Para uma conversa só, use chat-disappearing.

response
{
  "success": true
}

Exceções de privacidade

GET /instances/{id}/token/{token}/privacy/exceptions/{category}

Lista quem está nas exceções ("meus contatos, exceto…") de uma categoria: last-seen, photo-visualization, description ou group-add.

response
{
  "category": "photo-visualization",
  "phones": [
    "5511999999999"
  ]
}

Alterar exceções de privacidade

POST /instances/{id}/token/{token}/privacy/exceptions

Acrescenta (add) ou tira (remove) contatos das exceções de uma categoria. Alterar também coloca a categoria em "meus contatos, exceto…".

request
{
  "category": "photo-visualization",
  "action": "add",
  "phones": [
    "5511999999999"
  ]
}
response
{
  "value": true
}

Criar comunidade

POST /instances/{id}/token/{token}/create-community

Cria uma comunidade, com entrada por pedido. name é obrigatório. Devolve a comunidade no formato de groups.

request
{
  "name": "Clientes Exemplo",
  "description": "Avisos e novidades"
}
response
{
  "groupId": "120363000000000000@g.us",
  "name": "Clientes Exemplo",
  "description": "Avisos e novidades",
  "amAdmin": true,
  "participantCount": 1,
  "announce": false,
  "locked": false
}

Desativar comunidade

POST /instances/{id}/token/{token}/deactivate-community

Desativa a comunidade. Os grupos ligados continuam existindo, soltos.

request
{
  "communityId": "120363000000000000@g.us"
}
response
{
  "value": true
}

Transferir comunidade

POST /instances/{id}/token/{token}/transfer-community

Passa a posse da comunidade para outro membro (phone).

request
{
  "communityId": "120363000000000000@g.us",
  "phone": "5511999999999"
}
response
{
  "value": true
}

Participantes da comunidade

GET /instances/{id}/token/{token}/communities/{communityId}/participants

Lista, de uma vez, os participantes de todos os grupos da comunidade.

response
[
  "5511999999999",
  "5511988888888"
]

Entrar em grupo da comunidade

POST /instances/{id}/token/{token}/communities/join-group

Entra num grupo de uma comunidade da qual o número já é membro. announcement: true indica o grupo de avisos.

Atenção: não confirmado. No teste (voltar a um grupo do qual tínhamos saído), o servidor respondeu 405. Falta testar com uma conta que nunca esteve no grupo.
request
{
  "communityId": "120363000000000000@g.us",
  "groupId": "120363111111111111@g.us"
}
response
{
  "value": true
}

Histórico para novos membros

PUT /instances/{id}/token/{token}/group-history

Define quem pode mandar o histórico recente do grupo a quem entra: todos os membros (value: true) ou só admins (false).

request
{
  "groupId": "120363000000000000@g.us",
  "value": true
}
response
{
  "value": true
}

Etiqueta de membro

PUT /instances/{id}/token/{token}/group-member-label

Define a etiqueta deste número no grupo (ex.: "Suporte"), exibida sob o nome. label vazio remove. Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "groupId": "120363000000000000@g.us",
  "label": "Suporte"
}

Canal ao vivo

POST /instances/{id}/token/{token}/newsletter-live

Assina por alguns minutos as atualizações ao vivo de um canal (visualizações e reações por publicação), que chegam no webhook NewsletterLiveUpdate.

A resposta diz por quanto tempo a assinatura vale; depois, assine de novo.

request
{
  "newsletterId": "120363000000000000@newsletter"
}
response
{
  "durationSeconds": 90
}

Marcar publicação como vista

POST /instances/{id}/token/{token}/newsletter-mark-viewed

Conta visualização nas publicações do canal, pelos serverIds.

Atenção: aceito pelo servidor, mas não deu para conferir (o teste foi feito como dono do canal).
request
{
  "newsletterId": "120363000000000000@newsletter",
  "serverIds": [
    100,
    101
  ]
}
response
{
  "value": true
}

Reagir a publicação

POST /instances/{id}/token/{token}/newsletter-reaction

Reage a uma publicação do canal. reaction vazio tira a reação.

request
{
  "newsletterId": "120363000000000000@newsletter",
  "serverId": 100,
  "reaction": "👍"
}
response
{
  "value": true
}

Excluir canal

POST /instances/{id}/token/{token}/delete-newsletter

Exclui um canal do qual a instância é dona. id termina em @newsletter.

request
{
  "id": "120363000000000000@newsletter"
}
response
{
  "value": true
}

Quem vê o status

GET /instances/{id}/token/{token}/status-privacy

Diz quem vê os status do número: contacts (todos os contatos), whitelist (só a lista) ou blacklist (todos menos a lista).

response
{
  "type": "contacts"
}

Status com menção

POST /instances/{id}/token/{token}/send-status-mention

Publica um status de texto mencionando até 20 contatos; cada mencionado recebe o aviso de menção.

O status vai para o público da privacidade de status do número (veja status-privacy). Resposta: a padrão de todo send-* (zaapId, messageId, id).

request
{
  "message": "Novidades da semana!",
  "mentioned": [
    "5511999999999"
  ]
}

Apagar status

POST /instances/{id}/token/{token}/delete-status

Apaga um status publicado pela instância, pelo messageId.

request
{
  "messageId": "3EB0C9A1F2D4"
}
response
{
  "value": true
}

Marcar status como visto

POST /instances/{id}/token/{token}/mark-status-viewed

Marca o status de um contato como visto: ele passa a ver a instância na lista de quem viu. phone é o autor do status.

request
{
  "phone": "5511999999999",
  "messageId": "3EB0C9A1F2D4"
}
response
{
  "value": true
}

Listar IAs da Meta

GET /instances/{id}/token/{token}/bots

Lista as IAs da Meta disponíveis para a conta, com o perfil de cada uma quando o servidor devolve (nome, descrição, sugestões e comandos).

response
[
  {
    "phone": "<jid do bot>",
    "personaId": "<id da persona>",
    "name": "Meta AI",
    "description": "...",
    "isDefault": true,
    "prompts": [
      "..."
    ]
  }
]

Perguntar à Meta AI

POST /instances/{id}/token/{token}/ask-meta-ai

Manda a pergunta à Meta AI pelo número conectado e espera a resposta final na mesma chamada. timeoutSeconds: padrão 60, máximo 120.

Se a resposta não vier no prazo, a rota responde erro, e a resposta, se vier, chega pelo webhook como mensagem de texto. Só texto: imagem gerada não é tratada.

Atenção: a conversa com a Meta AI aparece no celular conectado, e a Meta AI lembra o contexto das perguntas anteriores dessa conversa. Use com moderação: muitas perguntas em volume podem chamar a atenção da Meta para o número.
request
{
  "question": "Me dê 3 dicas para alugar um carro",
  "timeoutSeconds": 60
}
response
{
  "questionMessageId": "3EB0C9A1F2D4",
  "answer": "1. Compare preços..."
}

Webhook — mensagem recebida

O Zapbot faz POST na sua webhookUrl a cada mensagem recebida, com type: "ReceivedCallback".

texto
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "connectedPhone": "5511777777777",
  "fromMe": false,
  "momment": 1720000000000,
  "senderName": "Maria",
  "isGroup": false,
  "text": { "message": "Oi!" }
}
imagem — mídia já re-hospedada
{
  "type": "ReceivedCallback",
  "phone": "5511999999999", /* ... */
  "image": {
    "imageUrl": "https://api.zapbot.online/media/uuid.jpg",
    "caption": "legenda",
    "mimeType": "image/jpeg"
  }
}

O campo que carrega o conteúdo muda conforme o tipo da mensagem:

Tipo Campo no payload
Texto text.message
Imagem image.imageUrl, image.caption
Áudio audio.audioUrl
Vídeo video.videoUrl, video.caption
Documento document.documentUrl, document.fileName
Figurinha sticker.stickerUrl
Localização location.latitude, location.longitude
Contato contact.displayName, contact.vCard
Reação reaction.value, reaction.referencedMessage.messageId
Enquete poll.question, poll.options[], poll.correctAnswer, poll.endTime, poll.hideVoters (detalhes)
Mensagem editada isEdit: true + conteúdo novo, com o messageId da original (detalhes)
Formulário respondido formResponse.fields (detalhes)
Pedido do carrinho order.orderId, order.token, order.totalAmount1000 (detalhes)
Confirmação de cobrança paymentConfirmation.referenceId, paymentConfirmation.method (detalhes)
Vinda de anúncio externalAdReply.ctwaClid, adAttribution — junto do conteúdo (detalhes)
Etiqueta na conversa notification: "CHAT_LABEL_ASSOCIATION", notificationParameters[] (detalhes)
Participantes de grupo GroupParticipantCallback com action, participants[], by e viaInviteLink — precisa de notifyGroups ligado (detalhes)
Os itens marcados como desligados por padrão só saem com o interruptor ligado na conexão (notifyGroups, notifyExtras, notifyContactStatus). Mergear uma versão nova da API não muda o que a sua conexão recebe.
Mensagem dentro de grupo, lista de transmissão, status e canal não gera webhook — apenas conversas 1:1. O que grupo gera é o GroupParticipantCallback, que é sobre a lista de participantes.
A mídia fica disponível no endereço do webhook por até 24 horas e depois é apagada. Baixe ao receber e guarde no seu armazenamento.

Webhook — status de mensagem

Acompanha o ciclo de entrega das mensagens que você enviou.

Sai um aviso por mensagem, pessoa e status (antes vinha um por aparelho de quem recebe). ids traz todas as mensagens do recibo; messageId é a primeira. Em grupo, isGroup: true e participantPhone diz quem recebeu ou leu — um aviso para cada participante.

MessageStatusCallback
{
  "type": "MessageStatusCallback",
  "status": "READ",   // SENT | RECEIVED | READ | PLAYED
  "phone": "120363000000000000",
  "messageId": "3EB0C9A1F2D4",
  "ids": [
    "3EB0C9A1F2D4"
  ],
  "isGroup": true,
  "participantPhone": "5511988888888"
}

Webhook — conexão

Disparado quando o número conclui o pareamento.

ConnectedCallback
{
  "type": "ConnectedCallback",
  "phone": "5511777777777",
  "connectedPhone": "5511777777777"
}

Webhook — mensagem editada

Quando o contato edita uma mensagem, chega um ReceivedCallback com isEdit: true. O messageId é o da mensagem original e o conteúdo é o novo: troque o texto da mensagem que você já tem com esse ID.

ReceivedCallback — edição
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "isEdit": true,
  "text": {
    "message": "Texto corrigido"
  }
}

Webhook — presença

Chega depois de subscribe-presence. status: AVAILABLE, UNAVAILABLE, COMPOSING, RECORDING ou PAUSED. lastSeen (ms) vem com UNAVAILABLE quando a privacidade do contato permite; senão, null.

PresenceChatCallback
{
  "type": "PresenceChatCallback",
  "phone": "5511999999999",
  "status": "COMPOSING",
  "lastSeen": null,
  "instanceId": "b1a2c3d4-..."
}

Webhook — resposta a evento

Confirmação de presença num evento enviado por send-event. eventId é o messageId do evento; response: GOING, NOT_GOING ou MAYBE; momment em ms.

Atenção: o formato está implementado, mas a chegada com uma resposta real ainda não foi conferida.
EventResponseCallback
{
  "type": "EventResponseCallback",
  "instanceId": "b1a2c3d4-...",
  "eventId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "response": "GOING",
  "extraGuests": 1,
  "momment": 1720000000000
}

Webhook — canal ao vivo

Chega enquanto vale a assinatura de newsletter-live: visualizações e contagem de reações por publicação (serverId).

NewsletterLiveUpdate
{
  "type": "NewsletterLiveUpdate",
  "instanceId": "b1a2c3d4-...",
  "newsletterId": "120363000000000000@newsletter",
  "momment": 1720000000000,
  "messages": [
    {
      "serverId": 100,
      "views": 12,
      "reactions": {
        "❤️": 1,
        "👍": 1
      }
    }
  ]
}

Webhook — resposta da Meta AI

A Meta AI responde em pedaços, editando a mesma mensagem. Os pedaços intermediários não viram webhook: só a versão final chega, como ReceivedCallback de texto comum, com o messageId da resposta do bot. Só chegam respostas a perguntas feitas por ask-meta-ai (até 10 minutos depois da espera); conversas com a Meta AI feitas no celular não viram webhook.

Os recibos de entrega das mensagens do bot não geram MessageStatusCallback.

ReceivedCallback — texto da Meta AI
{
  "type": "ReceivedCallback",
  "messageId": "3EB0C9A1F2D4",
  /* ... */
  "text": {
    "message": "A capital do Brasil é Brasília."
  }
}

Webhook — origem do anúncio e conversões

Na mensagem que o lead manda pelo botão de um anúncio Click-to-WhatsApp (Facebook ou Instagram), o ReceivedCallback traz externalAdReply com o anúncio de origem e o ctwaClid (identificador do clique). sourceId é o ID do anúncio.

Nas mensagens seguintes da conversa iniciada pelo anúncio, vem adAttribution (conversionSource, ex.: FB_Ads; conversionData é opaco, em base64).

Atenção: formato implementado; ainda não conferido com um lead real de anúncio numa conversa individual.
Atenção: o ctwaClid só vem na primeira mensagem vinda do anúncio: guarde na hora. E só vem com o compartilhamento de dados de anúncio ligado no app Business.
ReceivedCallback — primeira mensagem do anúncio
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "text": {
    "message": "Olá! Quero saber mais"
  },
  "externalAdReply": {
    "title": "Promoção de outubro",
    "body": "Fale com a gente",
    "mediaType": "IMAGE",
    "thumbnailUrl": "https://...",
    "sourceType": "ad",
    "sourceId": "120200000000000000",
    "ctwaClid": "ARAkLm...",
    "sourceUrl": "https://fb.me/...",
    "containsAutoReply": false,
    "renderLargerThumbnail": true,
    "showAdAttribution": true
  }
}
adAttribution — mensagens seguintes
{
  "adAttribution": {
    "conversionSource": "FB_Ads",
    "conversionData": "base64..."
  }
}

Reportar conversão à Meta

Etiqueta aplicada pela API não reporta conversão à Meta. No app Business, quem marca uma conversa de anúncio com etiqueta padrão (Novo pedido, Pago…) é o próprio aparelho, que envia o evento de conversão pela telemetria interna dele; a etiqueta que chega ao celular pela sincronização não dispara esse evento.

O caminho é a Conversions API da Meta para mensagens de negócios:

Passo O que fazer
1 Guarde o externalAdReply.ctwaClid do primeiro webhook do lead.
2 Ao concluir o fluxo (venda, lead qualificado…), envie o evento para POST https://graph.facebook.com/v{versão}/{DATASET_ID}/events com action_source: "business_messaging", messaging_channel: "whatsapp", user_data.ctwa_clid (o mesmo valor) e user_data.whatsapp_business_account_id, o evento (Purchase, LeadSubmitted, QualifiedLead…) e custom_data com valor e moeda.
3 Opcional: aplique também a etiqueta padrão pela API, só para organizar no celular.

Requisitos do lado da Meta: dataset (pixel) vinculado à conta WhatsApp Business (WABA) e token com a permissão whatsapp_business_manage_events. Sem ctwaClid (conversa orgânica), não envie. Referência: developers.facebook.com/docs/marketing-api/conversions-api/business-messaging.

Webhook — pedido do carrinho

Pedido montado pelo cliente no carrinho do catálogo. totalAmount1000 em milésimos (R$ 500,00 = 500000). Use orderId e token em order-details para ver os itens, e orderId como orderRequestId em send-charge.

ReceivedCallback — order
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "order": {
    "orderId": "1234567890123456",
    "token": "AR4b...",
    "itemCount": 1,
    "status": "INQUIRY",
    "title": "Mesa",
    "totalAmount1000": 500000,
    "currency": "BRL",
    "sellerJid": "5511777777777@s.whatsapp.net"
  }
}

Webhook — confirmação de cobrança

O cliente revisou e confirmou uma cobrança de send-charge. stanzaId é a mensagem da cobrança; params traz o JSON original (itens e valores) como veio.

ReceivedCallback — paymentConfirmation
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "paymentConfirmation": {
    "referenceId": "PED-1001",
    "method": "confirm",
    "orderStatus": "pending",
    "currency": "BRL",
    "stanzaId": "3EB0C9A1F2D4",
    "params": "{...}"
  }
}

Webhook — formulário respondido

Respostas de um formulário de send-form. stanzaId é a mensagem do formulário; em fields, a chave é o nome do campo no formulário (ou o rótulo, quando não há nome) e o valor é o que o cliente preencheu.

ReceivedCallback — formResponse
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "formResponse": {
    "stanzaId": "3EB0C9A1F2D4",
    "fields": {
      "<campo>": "<valor preenchido>"
    }
  }
}

Webhook — etiqueta na conversa

Quando uma etiqueta é aplicada ou removida de uma conversa (no celular ou pela API), chega um ReceivedCallback com notification: "CHAT_LABEL_ASSOCIATION". label é o ID da etiqueta; assigned: false indica remoção. Sai com o notifyExtras ligado na conexão (antes exigia notifySentByMe; a partir desta versão o interruptor é o notifyExtras).

ReceivedCallback — notification
{
  "type": "ReceivedCallback",
  "notification": "CHAT_LABEL_ASSOCIATION",
  "notificationParameters": [
    {
      "phone": "5511999999999",
      "label": "11",
      "assigned": true
    }
  ],
  "instanceId": "b1a2c3d4-...",
  /* ... */
}

Webhook — participantes do grupo

Avisa sempre que a lista de participantes de um grupo mudar, na conexão que tiver o notifyGroups ligado. Cada ação é uma chamada; uma entrada coletiva (mil pessoas pelo link) é uma chamada com mil números.

GroupParticipantCallback
{
  "type": "GroupParticipantCallback",
  "instanceId": "b1a2c3d4-...",
  "connectedPhone": "5511777777777",
  "groupId": "120363000000000000@g.us",
  "groupName": "AULÃO #01",
  "action": "add",
  "participants": ["5511999999999"],
  "by": "5511888888888",
  "viaInviteLink": true,
  "momment": 1720000000000
}
CampoDetalhe
action add | leave | remove | promote | demote. Um mesmo aviso do WhatsApp pode trazer mais de uma mudança (entrou e virou admin): nesse caso sai um webhook para cada.
participants Números afetados, já resolvidos de LID para telefone.
by Quem fez a ação. Vazio quando a pessoa entrou sozinha pelo link ou saiu por conta própria — não houve administrador nenhum.
viaInviteLink true quando a entrada foi pelo link de convite. Sai sempre no corpo, mesmo false, para "não entrou por link" e "campo ausente" não ficarem iguais.
groupName Vem de cache de 15 minutos por grupo — o aviso do WhatsApp não traz o nome, só o JID. Se a consulta falhar, sai vazio em vez de o aviso não sair.

leave ou remove: o WhatsApp não manda essa distinção em campo próprio — os dois chegam pelo mesmo aviso interno. Distinguimos pelo autor: quando quem age é a própria pessoa (ou ninguém), é leave; quando é outro, é remove.

Se dois dos seus números estiverem no mesmo grupo, cada instância avisa. Deduplique por groupId + participante + momment.

Webhook — enquete recebida

Além de question e options, a enquete recebida traz, quando existem: correctAnswer (quiz), endTime (encerramento, em segundos Unix) e hideVoters (votos ocultos).

ReceivedCallback — poll
{
  "type": "ReceivedCallback",
  "instanceId": "b1a2c3d4-...",
  "messageId": "3EB0C9A1F2D4",
  "phone": "5511999999999",
  "fromMe": false,
  "momment": 1720000000000,
  "poll": {
    "question": "Qual a capital do Brasil?",
    "options": [
      {
        "name": "Brasília"
      },
      {
        "name": "São Paulo"
      }
    ],
    "correctAnswer": "Brasília",
    "endTime": 1791205200,
    "hideVoters": true
  }
}

Zapbot — A API de WhatsApp mais estável do mercado, construída sobre whatsmeow. Precisa de ajuda? suporte@zapbot.online