Documentação da API

Envie e receba mensagens de WhatsApp por HTTP: texto, mídia, grupos e eventos em tempo real por webhook. Cada exemplo tem cURL, Node.js e Python prontos para copiar.

URL base
zap.lovazero.shop/api/v1
Autenticação
Bearer lzz_…
Formato
JSON · UTF-8

Visão geral

A API do LovaZero Zap envia e recebe mensagens de WhatsApp por HTTP. Cada instância é um número conectado pelo QR Code (ou código de pareamento) no painel, com uma chave própria. Tudo é JSON em UTF-8.

URL base
https://zap.lovazero.shop/api/v1
  1. Compre uma ou mais instâncias no painel (PIX, ciclo de 30 dias).
  2. Abra a instância e conecte o número lendo o QR Code no WhatsApp do celular.
  3. Gere a chave da instância (ela aparece uma única vez) e guarde no seu servidor.
  4. Chame as rotas abaixo com a chave no cabeçalho.

API não oficial

O motor usa o protocolo do WhatsApp Web (biblioteca Baileys). Não é a API oficial do WhatsApp Business nem tem ligação com a Meta. O WhatsApp pode restringir ou banir números que mandam spam ou mensagens em massa não solicitadas, e esse risco é de quem usa o número. Uso para spam ou golpe leva ao cancelamento da instância. Leia os termos.

Autenticação

Toda chamada leva a chave da instância, de um destes dois jeitos (tanto faz qual):

Cabeçalhos aceitos
Authorization: Bearer lzz_sua_chave_aqui
X-Api-Key: lzz_sua_chave_aqui

A chave identifica a instância: a URL nunca leva id de sessão. Com duas instâncias, você tem duas chaves e usa a de cada número. A chave começa com lzz_, aparece inteira só na hora em que é gerada e pode ser trocada no painel a qualquer momento; a antiga para de funcionar na hora.

Guarde a chave numa variável de ambiente do seu servidor (os exemplos usam LZZ_CHAVE). Não coloque a chave em código que roda no navegador: quem abrir o site copia a chave e, forjando o Origin, passa pela trava de domínio (veja Domínios, IPs e CORS).

Sua instância

Diz de qual instância é a chave e como ela está. Responde mesmo com a instância vencida, suspensa ou desconectada.

GET/api/v1/instancia
curl "https://zap.lovazero.shop/api/v1/instancia" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
{
  "id": "ins_4f9c2a7b1d3e",
  "nome": "Atendimento",
  "status": "conectada",
  "numero": "5511999999999",
  "expiraEm": "2026-10-30T14:02:11.000Z"
}
statusO que quer dizer
conectadaPronta para enviar e receber.
qrEsperando a leitura do QR Code.
conectandoO motor está subindo a sessão.
desconectadaNunca conectou ou saiu do aparelho; conecte de novo.
expiradaVenceu e não foi renovada: as outras rotas respondem 402.
suspensaSuspensa pelo suporte: as outras rotas respondem 403.

Formato do chatId

As rotas de mensagem apontam o destino pelo chatId:

  • Contato: DDI + DDD + número, só dígitos, seguido de @c.us. Ex.: 5511988887777@c.us.
  • Grupo: o id do grupo, que termina em @g.us. Ex.: 120363025246125486@g.us (pegue em listar grupos).

No Brasil, alguns números antigos estão no WhatsApp sem o nono dígito. Na dúvida, confira com Verificar número: ele devolve o whatsappId certo para usar como chatId.

Algumas mensagens recebidas podem trazer o remetente como …@lid (id de privacidade do WhatsApp). Nesses casos o webhook traz também senderPhone com o telefone, quando o WhatsApp revela (senão, null). Para um id avulso, use GET /contacts/{contactId}/phone.

Conectar o número

A primeira conexão é feita no painel: ele cria a sessão e mostra o QR Code. Antes disso, as rotas respondem 409 instancia_nao_conectada. Depois, se o número cair, dá para pedir um novo QR ou um código de pareamento pela API.

QR Code

GET/api/v1/qr

qrCode vem como imagem PNG em data URL (pode ir direto num <img src>). O QR muda a cada poucos segundos; consulte de novo enquanto o status for qr_ready.

curl "https://zap.lovazero.shop/api/v1/qr" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
{
  "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA…",
  "status": "qr_ready"
}

Código de pareamento

POST/api/v1/pairing-code

Alternativa ao QR: no celular, abra WhatsApp → Aparelhos conectados → Conectar aparelho → Conectar com número de telefone e digite o código de 8 caracteres.

Corpo (JSON)

phoneNumberstringobrigatório
O número que vai conectar, só dígitos com DDI: 5511999999999.
curl -X POST "https://zap.lovazero.shop/api/v1/pairing-code" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "5511999999999"
  }'
Resposta 201
{
  "pairingCode": "K7Q2M9XD",
  "status": "qr_ready"
}

Ligar, parar e sair

RotaO que faz
POST /startSobe a sessão de novo (quando ela foi parada).
POST /stopPara a sessão sem desconectar o aparelho; o start volta sem QR.
POST /logoutDesconecta o aparelho do WhatsApp. Para voltar, precisa de um novo QR ou código.

Status da sessão nessas respostas: created, initializing, qr_ready, authenticating, ready, disconnected, action_required ou failed.

Enviar texto

POST/api/v1/messages/send-text

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
textstringobrigatório
O texto, até 4096 caracteres. Aceita a formatação do WhatsApp (*negrito*, _itálico_).
mentionsstring[]
Contatos mencionados (chatIds). Em grupo, escreva também @5511988887777 no texto.
linkPreviewboolean
Gera a prévia do primeiro link do texto.
quotedMessageIdstring
Id de uma mensagem da conversa para aparecer citada (resposta).
curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-text" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "text": "Olá, Maria! Seu pedido saiu para entrega."
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Guarde o messageId: é com ele que você reage, responde, apaga ou acompanha a entrega (evento message.ack).

Enviar imagem

POST/api/v1/messages/send-image

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
urlstring
Endereço público (https) do arquivo. O servidor baixa e envia. Use url ou base64.
base64string
O arquivo em base64 (sem o prefixo data:). Se vier junto com url, vale o base64.
mimetypestring
Tipo do arquivo, ex.: image/jpeg. Obrigatório na prática quando manda base64.
filenamestring
Nome do arquivo que aparece para quem recebe.
captionstring
Legenda, até 1024 caracteres.
mentionsstring[]
Contatos mencionados na legenda (chatIds).
quotedMessageIdstring
Id de uma mensagem da conversa para aparecer citada (resposta).
curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-image" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "url": "https://meusite.com.br/catalogo/tenis-azul.jpg",
    "caption": "Chegou o azul no seu número!"
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Prefira url: o corpo fica pequeno e o servidor baixa o arquivo. Em base64, o arquivo cresce cerca de um terço.

Enviar documento

POST/api/v1/messages/send-document

Mesmos campos da imagem. Informe filename para o arquivo chegar com nome.

curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-document" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "url": "https://meusite.com.br/boletos/2026-10.pdf",
    "mimetype": "application/pdf",
    "filename": "boleto-outubro.pdf",
    "caption": "Seu boleto de outubro."
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Enviar áudio e voz

POST/api/v1/messages/send-audio

Mesmos campos da imagem, mais ptt. Com "ptt": true o áudio chega como mensagem de voz (a bolinha do microfone); sem ele, como arquivo de áudio.

Campo a mais

pttboolean
true = mensagem de voz. Sem mimetype, o servidor assume audio/ogg; codecs=opus.

Mensagem de voz só toca se o arquivo for OGG com Opus. O motor não converte: um MP3 com ptt chega como bolinha que não toca. Converta antes (ex.: ffmpeg -i audio.mp3 -c:a libopus -ac 1 voz.ogg). GET /media/convert diz se a conversão no servidor está ligada ({"available": true}).

curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-audio" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "url": "https://meusite.com.br/audios/boas-vindas.ogg",
    "mimetype": "audio/ogg; codecs=opus",
    "ptt": true
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Enviar vídeo

POST/api/v1/messages/send-video

Mesmos campos da imagem. Use MP4 (H.264 + AAC), o formato que o WhatsApp toca em todos os aparelhos.

curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-video" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "url": "https://meusite.com.br/videos/como-usar.mp4",
    "caption": "Veja como usar em 1 minuto."
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Enviar localização

POST/api/v1/messages/send-location

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
latitudenumberobrigatório
Latitude em graus decimais.
longitudenumberobrigatório
Longitude em graus decimais.
addressstring
Texto que aparece embaixo do mapa (até 1024 caracteres).
quotedMessageIdstring
Id de uma mensagem da conversa para aparecer citada (resposta).
curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-location" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "latitude": -23.561414,
    "longitude": -46.655881,
    "address": "Av. Paulista, 1578 — São Paulo"
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Enviar contato

POST/api/v1/messages/send-contact

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
contactNamestringobrigatório
Nome do cartão de contato (até 255 caracteres).
contactNumberstringobrigatório
Telefone do contato, com DDI (até 30 caracteres).
quotedMessageIdstring
Id de uma mensagem da conversa para aparecer citada (resposta).
curl -X POST "https://zap.lovazero.shop/api/v1/messages/send-contact" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "contactName": "Suporte Loja Azul",
    "contactNumber": "5511977776666"
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Responder mensagem

POST/api/v1/messages/reply

Responde citando uma mensagem (o balão da original aparece em cima).

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
quotedMessageIdstringobrigatório
Id da mensagem citada (o data.id do webhook).
textstringobrigatório
O texto da resposta, até 4096 caracteres.
mentionsstring[]
Contatos mencionados (chatIds).
curl -X POST "https://zap.lovazero.shop/api/v1/messages/reply" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "quotedMessageId": "3EB0C767D26B8A3F1A2B",
    "text": "Abrimos sim, das 9h às 13h."
  }'
Resposta 201
{
  "messageId": "3EB0C767D26B8A3F1A2B",
  "timestamp": 1790776931
}

Reagir

POST/api/v1/messages/react

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
messageIdstringobrigatório
Id da mensagem que recebe a reação.
emojistringobrigatório
O emoji. Para tirar a reação, envie vazio: "".
curl -X POST "https://zap.lovazero.shop/api/v1/messages/react" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "messageId": "3EB0C767D26B8A3F1A2B",
    "emoji": "👍"
  }'
Resposta 200
{
  "success": true
}

Marcar como lida

POST/api/v1/chats/read

Manda o “visto” (os dois tiques azuis) para quem escreveu.

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
messageIdsstring[]
Ids das mensagens recebidas a marcar (1 a 100). No Baileys o visto é por mensagem: sem esta lista, só a mais recente que o motor ainda tem na memória é marcada. Guarde os data.id do webhook e envie aqui.
curl -X POST "https://zap.lovazero.shop/api/v1/chats/read" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "messageIds": [
      "3EB0C767D26B8A3F1A2B"
    ]
  }'
Resposta 200
{
  "success": true
}

Apagar mensagem

POST/api/v1/messages/delete

Corpo (JSON)

chatIdstringobrigatório
Quem recebe: contato 5511988887777@c.us ou grupo …@g.us. Veja Formato do chatId.
messageIdstringobrigatório
Id da mensagem enviada por este número.
forEveryoneboolean
Apaga para todos (padrão true). false apaga só neste aparelho.
curl -X POST "https://zap.lovazero.shop/api/v1/messages/delete" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "chatId": "5511988887777@c.us",
    "messageId": "3EB0C767D26B8A3F1A2B",
    "forEveryone": true
  }'
Resposta 200
{
  "success": true
}

O WhatsApp só deixa apagar para todos por um tempo depois do envio; passado esse prazo, a mensagem continua para quem recebeu.

Verificar número

GET/api/v1/contacts/check/{numero}

Diz se um telefone tem WhatsApp e devolve o whatsappId para usar como chatId. Número só com dígitos e DDI.

curl "https://zap.lovazero.shop/api/v1/contacts/check/5511988887777" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
{
  "number": "5511988887777",
  "exists": true,
  "whatsappId": "5511988887777@c.us"
}

Sem WhatsApp: "exists": false e "whatsappId": null. Não use esta rota para varrer listas de números: o WhatsApp trata isso como comportamento suspeito.

Listar conversas

GET/api/v1/chats

Conversas ativas, com não lidas e última mensagem. Pagine com limit e offset. timestamp é em segundos.

curl "https://zap.lovazero.shop/api/v1/chats?limit=20&offset=0" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
[
  {
    "id": "5511988887777@c.us",
    "name": "Maria",
    "isGroup": false,
    "kind": "individual",
    "unreadCount": 2,
    "timestamp": 1790776931,
    "lastMessage": "Vocês abrem sábado?",
    "archived": false,
    "pinned": false,
    "muted": false
  }
]

Mensagens de uma conversa

GET/api/v1/messages

Mensagens que o motor guardou desta instância, da mais nova para a mais antiga.

ParâmetroO que faz
chatIdSó as mensagens desta conversa.
limitQuantas trazer (padrão 50).
offsetPula as primeiras N (paginação).
afterCursor: o id da última mensagem da página anterior. Mais estável que offset.
curl "https://zap.lovazero.shop/api/v1/messages?chatId=5511988887777@c.us&limit=50" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
{
  "messages": [
    {
      "id": "0a941dac-a965-45e7-b318-74ae8be134f0",
      "waMessageId": "3EB0C767D26B8A3F1A2B",
      "chatId": "5511988887777@c.us",
      "chatName": "Maria",
      "from": "5511999999999@c.us",
      "to": "5511988887777@c.us",
      "body": "Olá, Maria! Seu pedido saiu para entrega.",
      "type": "text",
      "direction": "outgoing",
      "timestamp": 1790776931,
      "status": "delivered",
      "createdAt": "2026-09-30T14:02:11.000Z"
    }
  ],
  "total": 1
}

Aqui o id é o registro interno; para reagir, responder ou apagar use o waMessageId. Mensagens com mídia trazem o arquivo em GET /messages/{chatId}/{messageId}/media.

Criar e listar grupos

GET/api/v1/groups
curl "https://zap.lovazero.shop/api/v1/groups" \
  -H "Authorization: Bearer $LZZ_CHAVE"
Resposta 200
[
  {
    "id": "120363025246125486@g.us",
    "name": "Clientes VIP",
    "linkedParentJID": null
  }
]
POST/api/v1/groups

Corpo (JSON)

namestringobrigatório
Nome do grupo (até 100 caracteres).
participantsstring[]obrigatório
chatIds de quem entra (até 256). O número da instância vira admin.
curl -X POST "https://zap.lovazero.shop/api/v1/groups" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Clientes VIP",
    "participants": [
      "5511988887777@c.us",
      "5511977776666@c.us"
    ]
  }'
Resposta 201
{
  "id": "120363025246125486@g.us",
  "name": "Clientes VIP",
  "participantsCount": 3,
  "isAdmin": true
}

Detalhes (participantes, admins, configurações): GET /groups/{groupId}. Link de convite: GET /groups/{groupId}/invite-code.

Participantes

RotaO que faz
POST /groups/{groupId}/participantsAdiciona.
DELETE /groups/{groupId}/participantsRemove.
POST /groups/{groupId}/participants/promoteTorna admin.
POST /groups/{groupId}/participants/demoteTira de admin.

Todas recebem {"participants": [chatIds]} (até 256) e respondem o resultado de cada um. O número da instância precisa ser admin do grupo.

curl -X POST "https://zap.lovazero.shop/api/v1/groups/120363025246125486@g.us/participants" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "participants": [
      "5511966665555@c.us"
    ]
  }'
Resposta 200
{
  "success": true,
  "message": "Participants added",
  "results": [
    {
      "id": "5511966665555@c.us",
      "success": true,
      "status": 200,
      "message": "ok"
    }
  ]
}

Um item pode falhar sozinho (ex.: a pessoa só aceita entrar por convite): confira results[].success de cada número.

Nome, recado e foto

RotaCorpoO que faz
PUT /profile/name{"name": "Loja Azul"}Nome de exibição (até 25 caracteres).
PUT /profile/status{"status": "Atendimento das 9h às 18h"}Recado (até 139 caracteres).
PUT /profile/picture{"url": "https://…/logo.jpg"}Foto do perfil (url, ou base64 + mimetype).
DELETE /profile/picture—Tira a foto.
curl -X PUT "https://zap.lovazero.shop/api/v1/profile/status" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "Atendimento das 9h às 18h"
  }'
Resposta 200
{
  "success": true,
  "message": "Profile status updated"
}

Criar e gerenciar

Webhook é o endereço do seu sistema que recebe um POST a cada evento (mensagem nova, entrega, conexão…). Há dois jeitos de configurar:

  • No painel (Instância → Webhook): URL e eventos. Simples, sem assinatura.
  • Pela API: além de URL e eventos, aceita secret (assina cada entrega), cabeçalhos próprios e número de tentativas. Pode ter mais de um por instância.
POST/api/v1/webhooks

Corpo (JSON)

urlstringobrigatório
Endereço público do seu sistema. Endereços internos (localhost, rede privada) e URL com usuário:senha são recusados com 400.
eventsstring[]
Eventos a receber (veja a lista abaixo). Padrão: ["message.received"]. ["*"] = todos.
secretstring
Segredo de 16 a 255 caracteres. Com ele, cada entrega leva X-OpenWA-Signature. Nunca é devolvido pela API.
headersobject
Cabeçalhos extras enviados em cada entrega, ex.: {"Authorization": "Bearer …"}.
retryCountnumber
Tentativas por evento, contando a primeira (0 a 5, padrão 3).
curl -X POST "https://zap.lovazero.shop/api/v1/webhooks" \
  -H "Authorization: Bearer $LZZ_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://meusite.com.br/webhook/whatsapp",
    "events": [
      "message.received",
      "message.ack",
      "session.status"
    ],
    "secret": "troque-por-um-segredo-longo-e-aleatorio"
  }'
Resposta 201
{
  "id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
  "sessionId": "8f14e45f-ceea-4c6b-9a3e-2d0c7b1e5a90",
  "url": "https://meusite.com.br/webhook/whatsapp",
  "events": [
    "message.received",
    "message.ack",
    "session.status"
  ],
  "filters": null,
  "active": true,
  "retryCount": 3,
  "lastTriggeredAt": null,
  "createdAt": "2026-09-30T14:02:11.000Z",
  "updatedAt": "2026-09-30T14:02:11.000Z"
}
RotaO que faz
GET /webhooksLista os webhooks da instância.
PUT /webhooks/{id}Muda só o que vier (active: false pausa sem apagar).
DELETE /webhooks/{id}Apaga.
POST /webhooks/{id}/testManda um evento test e diz o status que seu sistema respondeu.

Eventos e payload

EventoQuando chega
message.receivedChegou uma mensagem.
message.sentSaiu uma mensagem deste número.
message.ackMudou a entrega de uma mensagem enviada: pending, sent, delivered, read ou failed.
message.failedUma mensagem enviada falhou.
message.revokedUma mensagem foi apagada para todos.
message.reactionAlguém reagiu (ou tirou a reação) a uma mensagem.
message.editedUma mensagem foi editada.
session.statusA sessão mudou de estado (qr_ready, ready, disconnected…).
session.qrSaiu um QR Code novo (data URL de PNG).
session.authenticatedO número conectou.
session.disconnectedO número desconectou (queda ou saída pelo celular).
session.reconnect_loopA sessão tenta voltar sem conseguir (a cada 5 tentativas).
session.restrictionO WhatsApp restringiu a conta, ou tirou a restrição.
presence.updateAlguém ficou online, está digitando ou gravando (precisa de POST /presence/subscribe antes).
group.joinEntrou gente num grupo.
group.leaveSaiu gente de um grupo.
group.updateMudou nome, descrição ou configuração de um grupo.
group.join_requestPediram para entrar num grupo que você administra.
call.receivedChamada de voz ou vídeo tocando.
call.accepted · call.rejected · call.missedComo a chamada terminou.
status.receivedUm contato postou um status. Só chega se você pedir este evento (ou *).
*Todos os eventos acima.

Toda entrega tem o mesmo envelope; o que muda é o data. Exemplo de mensagem recebida:

POST no seu endereço · message.received
{
  "event": "message.received",
  "timestamp": "2026-09-30T14:02:11.418Z",
  "sessionId": "8f14e45f-ceea-4c6b-9a3e-2d0c7b1e5a90",
  "idempotencyKey": "msg_8f14e45f-ceea-4c6b-9a3e-2d0c7b1e5a90_3EB0A1B2C3D4E5F60718_d2f1c3b4-5a6e-4f70-8a91-b2c3d4e5f607",
  "deliveryId": "dlv_550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "3EB0A1B2C3D4E5F60718",
    "from": "5511988887777@c.us",
    "to": "5511999999999@c.us",
    "chatId": "5511988887777@c.us",
    "body": "Oi! Vocês abrem sábado?",
    "type": "text",
    "timestamp": 1790776931,
    "fromMe": false,
    "isGroup": false,
    "kind": "individual",
    "isStatusBroadcast": false,
    "contact": {
      "pushName": "Maria"
    }
  }
}
  • data.id é o id da mensagem: use em reagir, responder, apagar e marcar como lida. data.timestamp é em segundos; o timestamp de fora é a hora do envio do webhook.
  • sessionId é o id interno da sessão no motor (fixo por instância). Para separar números, use endereços diferentes ou guarde esse id.
  • Em grupo, chatId é o grupo e author é quem escreveu. Mensagens com mídia trazem media com mimetype e o arquivo em data (base64) quando é pequeno; arquivos maiores chegam com "omitted": true e você baixa em GET /messages/{chatId}/{messageId}/media.

Cabeçalhos de cada entrega

CabeçalhoO que é
X-OpenWA-EventO nome do evento.
X-OpenWA-Idempotency-KeyIgual em todas as tentativas do mesmo evento: use para não processar duas vezes.
X-OpenWA-Delivery-IdId da entrega.
X-OpenWA-Retry-Count0 na primeira tentativa, 1 na segunda…
X-OpenWA-SignatureSó com secret: sha256= + HMAC em hexadecimal.

Entrega “pelo menos uma vez”

Responda com status 2xx em até 10 segundos. Resposta de erro, demora ou falha de rede geram nova tentativa, até o retryCount. Por isso o mesmo evento pode chegar mais de uma vez: ignore o que já processou pelo X-OpenWA-Idempotency-Key.

Assinatura HMAC

Com secret, cada entrega leva X-OpenWA-Signature: sha256=<hex>: um HMAC-SHA256 dos bytes exatos do corpo, usando o seu segredo. Calcule sobre o corpo cru (não sobre o JSON já convertido e reescrito) e compare em tempo constante. Sem secret, o cabeçalho não vem.

Node.js (Express)
import crypto from "node:crypto";
import express from "express";


const app = express();


// A assinatura é sobre os bytes exatos do corpo: leia o corpo CRU.
app.post("/webhook/whatsapp", express.raw({ type: "application/json" }), (req, res) => {
  const recebida = Buffer.from(req.get("X-OpenWA-Signature") ?? "");
  const esperada = Buffer.from(
    "sha256=" + crypto.createHmac("sha256", process.env.LZZ_WEBHOOK_SEGREDO).update(req.body).digest("hex"),
  );
  if (recebida.length !== esperada.length || !crypto.timingSafeEqual(recebida, esperada)) {
    return res.sendStatus(401);
  }


  const evento = JSON.parse(req.body.toString("utf8"));
  if (evento.event === "message.received" && !evento.data.fromMe) {
    console.log(evento.data.chatId, evento.data.body);
  }
  res.sendStatus(200); // responda rápido; trabalho pesado vai para uma fila
});


app.listen(3000);
Python (Flask)
import hashlib
import hmac
import os


from flask import Flask, abort, request


app = Flask(__name__)
SEGREDO = os.environ["LZZ_WEBHOOK_SEGREDO"].encode()




@app.post("/webhook/whatsapp")
def webhook():
    corpo = request.get_data()  # bytes exatos do corpo
    esperada = "sha256=" + hmac.new(SEGREDO, corpo, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(request.headers.get("X-OpenWA-Signature", ""), esperada):
        abort(401)


    evento = request.get_json()
    if evento["event"] == "message.received" and not evento["data"]["fromMe"]:
        print(evento["data"]["chatId"], evento["data"]["body"])
    return "", 200

Erros

Erros da nossa porta de entrada vêm no formato {"erro": "codigo", "detalhe": "frase pronta"}. O detalhe pode ser mostrado ao usuário; decida pelo erro.

Resposta 402
{
  "erro": "instancia_expirada",
  "detalhe": "Esta instância venceu em 30/09/2026. Renove no painel."
}
StatuserroQuando
401chave_invalidaChave ausente, errada, trocada ou de instância apagada.
402instancia_expiradaA instância venceu. Renove no painel e a API volta na hora.
403instancia_suspensaA instância foi suspensa pelo suporte.
403origem_nao_autorizadaA chamada não veio de um domínio ou IP liberado para a instância.
404rota_desconhecidaCaminho ou método que a API não tem (confira em Todas as rotas).
409instancia_nao_conectadaA instância nunca foi conectada. Conecte no painel.
429limite_excedidoPassou do limite. Espere os segundos do cabeçalho Retry-After.
503motor_indisponivelO serviço de WhatsApp está fora do ar. Tente de novo em instantes.
503motor_ocupadoMuita demanda no serviço agora. Espere o Retry-After (2 s) e tente de novo.
504tempo_esgotadoO WhatsApp não respondeu em 60 segundos. Tente de novo.

A ordem das checagens é: chave (401) → suspensa (403) → vencida (402) → conectada (409) → domínio/IP (403) → limite (429). Toda resposta leva X-Request-Id; mande esse valor quando falar com o suporte.

Erros do WhatsApp

Passando pela porta de entrada, a resposta vem do motor do WhatsApp, com o formato dele:

Resposta 400
{
  "statusCode": 400,
  "message": [
    "text should not be empty"
  ],
  "error": "Bad Request"
}
  • 400: corpo inválido: campo obrigatório faltando, tipo errado ou campo que não existe (campos a mais são recusados). Também quando a sessão ainda não está ativa.
  • 404: grupo, mensagem ou contato não encontrado.
  • 409: a sessão não está pronta (conectando, sem internet no aparelho). Pode tentar de novo.
  • 501: o recurso não existe no motor Baileys (marcado em Todas as rotas).

Domínios, IPs e CORS

No painel, cada instância pode limitar de onde vêm as chamadas. Lista vazia = sem restrição. Com regras, a chamada passa se bater em qualquer uma:

  • Domínios: comparados com o Origin da chamada (ou o Referer, se não houver Origin), sem porta nem esquema. *.meusite.com.br vale para os subdomínios, não para meusite.com.br: cadastre os dois se precisar.
  • IPs: IPv4 ou IPv6 exatos, ou faixa IPv4 em CIDR (ex.: 198.51.100.0/24).
  • Até 20 domínios e 20 IPs por instância. Recusa = 403 origem_nao_autorizada.

A trava por domínio só impede outros sites de usarem a chave dentro de um navegador. Fora dele qualquer um inventa o Origin, então ela não protege uma chave que vazou. Para o seu backend, restrinja por IP. E, com as duas listas preenchidas, basta bater em uma: um Origin forjado passa mesmo vindo de um IP fora da lista.

CORS

O OPTIONS (preflight) responde 204 liberando Authorization, X-Api-Key e Content-Type. A resposta de verdade só leva Access-Control-Allow-Origin quando a chamada passou pelas restrições; aí também expõe X-Request-Id, Retry-After e Content-Disposition. Lembre: chave usada no navegador fica visível para quem abrir o site. Sempre que der, chame a API do seu servidor.

Limites

LimiteValorAo passar
Chamadas por segundo20 por instância429 com Retry-After: 1
Chamadas por minuto600 por instância429 com Retry-After: 60
Tempo de resposta do WhatsApp60 segundos504 tempo_esgotado
Arquivo de mídia (url ou base64)50 MB por arquivo413 Payload Too Large

Esses limites protegem o serviço. Quem decide se o número é banido é o WhatsApp: mandar muitas mensagens iguais para quem não pediu é o caminho mais rápido para perder o número. Espalhe os envios no tempo e fale com quem quer ouvir de você.

Todas as rotas

Tudo o que a API aceita. Cada caminho vem depois de /api/v1 e usa a chave da instância, como nos exemplos acima. Qualquer outro caminho responde 404 rota_desconhecida.

Conexão

  • POST/logout

    Desconecta o aparelho (vai precisar de novo QR).

  • POST/pairing-code

    Código de 8 caracteres para conectar pelo número.

  • GET/qr

    QR Code atual para conectar.

  • POST/start

    Sobe a sessão (depois de um stop).

  • POST/stop

    Para a sessão sem desconectar o aparelho.

Mensagens

  • GET/messages

    Mensagens guardadas (filtro por chatId, paginação).

  • GET/messages/{chatId}/{messageId}/media

    Baixa a mídia de uma mensagem.

  • GET/messages/batch/{batchId}

    Andamento de um lote.

  • POST/messages/batch/{batchId}/cancel

    Cancela um lote em andamento.

  • POST/messages/click-button

    Toca num botão de uma mensagem de empresa.

  • POST/messages/delete

    Apaga uma mensagem.

  • POST/messages/edit

    Edita o texto de uma mensagem enviada.

  • POST/messages/forward

    Encaminha uma mensagem para outra conversa.

  • POST/messages/pin

    Fixa uma mensagem na conversa.

  • POST/messages/react

    Reage (ou tira a reação).

  • POST/messages/reply

    Responde citando uma mensagem.

  • POST/messages/send-audio

    Envia áudio ou mensagem de voz (ptt).

  • POST/messages/send-bulk

    Envia para vários destinos em lote (assíncrono).

  • POST/messages/send-contact

    Envia cartão de contato.

  • POST/messages/send-document

    Envia documento.

  • POST/messages/send-image

    Envia imagem.

  • POST/messages/send-location

    Envia localização.

  • POST/messages/send-poll

    Envia enquete.

  • POST/messages/send-product

    Envia produto do catálogo (conta Business).

  • POST/messages/send-sticker

    Envia figurinha (imagem vira WebP).

  • POST/messages/send-template

    Envia um modelo de texto salvo em /templates.

  • POST/messages/send-text

    Envia texto.

  • POST/messages/send-video

    Envia vídeo.

  • POST/messages/star

    Marca ou desmarca com estrela.

  • POST/messages/unpin

    Desafixa.

Conversas

  • GET/chats

    Conversas ativas.

  • DELETE/chats/{chatId}/messages

    Limpa as mensagens da conversa.

  • POST/chats/archive

    Arquiva ou desarquiva.

  • POST/chats/delete

    Tira a conversa da lista.

  • POST/chats/mute

    Silencia ou tira o silêncio.

  • POST/chats/pin

    Fixa ou desafixa no topo.

  • POST/chats/read

    Marca como lida.

  • POST/chats/typing

    Mostra “digitando…” ou “gravando…” (ou para).

  • POST/chats/unread

    Marca como não lida.

Contatos

  • GET/contacts

    Contatos.

  • DELETE/contacts/{contactId}

    Tira da agenda.

  • GET/contacts/{contactId}

    Um contato.

  • PUT/contacts/{contactId}

    Salva ou edita na agenda.

  • DELETE/contacts/{contactId}/block

    Desbloqueia.

  • POST/contacts/{contactId}/block

    Bloqueia.

  • GET/contacts/{contactId}/phone

    Telefone de um id @lid.

  • GET/contacts/{contactId}/profile-picture

    Foto de perfil.

  • GET/contacts/blocked

    Contatos bloqueados.

  • GET/contacts/check/{numero}

    O número tem WhatsApp?

  • GET/contacts/profile-pictures

    Fotos de até 50 contatos de uma vez.

Grupos

  • GET/groups

    Grupos do número.

  • POST/groups

    Cria grupo.

  • GET/groups/{groupId}

    Detalhes e participantes.

  • PUT/groups/{groupId}/description

    Muda a descrição.

  • GET/groups/{groupId}/invite-code

    Código e link de convite.

  • POST/groups/{groupId}/invite-code/revoke

    Troca o link de convite.

  • POST/groups/{groupId}/leave

    Sai do grupo.

  • GET/groups/{groupId}/membership-requests

    Pedidos para entrar.

  • POST/groups/{groupId}/membership-requests/approve

    Aprova pedidos.

  • POST/groups/{groupId}/membership-requests/reject

    Recusa pedidos.

  • DELETE/groups/{groupId}/participants

    Remove participantes.

  • POST/groups/{groupId}/participants

    Adiciona participantes.

  • POST/groups/{groupId}/participants/demote

    Tira de admin.

  • POST/groups/{groupId}/participants/promote

    Torna admin.

  • DELETE/groups/{groupId}/picture

    Tira a foto.

  • GET/groups/{groupId}/picture

    Foto do grupo.

  • PUT/groups/{groupId}/picture

    Troca a foto.

  • GET/groups/{groupId}/settings

    Configurações (só admins, mensagens temporárias…).

  • PUT/groups/{groupId}/settings

    Altera as configurações.

  • PUT/groups/{groupId}/subject

    Muda o nome.

  • POST/groups/join

    Entra por código de convite.

  • GET/groups/join-info

    Prévia de um grupo pelo convite.

Perfil

  • PUT/profile/name

    Nome de exibição.

  • DELETE/profile/picture

    Tira a foto de perfil.

  • PUT/profile/picture

    Foto de perfil.

  • PUT/profile/status

    Recado.

Presença

  • PUT/presence

    Aparecer online ou offline.

  • GET/presence/{chatId}

    Última presença vista de uma conversa.

  • POST/presence/subscribe

    Passa a receber presence.update de uma conversa.

Status (stories)

  • GET/status

    Status (stories) recebidos dos contatos.

  • DELETE/status/{statusId}

    Apaga um status seu.

  • GET/status/{contactId}

    Status de um contato.

  • GET/status/{statusId}/media

    Mídia de um status.

  • POST/status/send-image

    Posta status com imagem.

  • POST/status/send-text

    Posta status de texto.

  • POST/status/send-video

    Posta status com vídeo.

  • POST/status/send-voice

    Posta status de voz.

Etiquetas

  • DELETE/labels/{labelId}

    Apaga etiqueta.

  • PUT/labels/{labelId}

    Cria ou edita etiqueta.

  • POST/labels/chat/{chatId}

    Põe etiqueta na conversa.

  • DELETE/labels/chat/{chatId}/{labelId}

    Tira etiqueta da conversa.

Canais

  • POST/channels

    Cria canal.

  • DELETE/channels/{channelId}

    Deixa de seguir.

  • GET/channels/{channelId}

    Um canal.

  • POST/channels/{channelId}/admins/demote

    Tira um admin do canal.

  • POST/channels/{channelId}/delete

    Apaga um canal seu.

  • POST/channels/{channelId}/mute

    Silencia ou tira o silêncio.

  • POST/channels/{channelId}/owner/transfer

    Passa o canal para outra conta.

  • POST/channels/subscribe

    Segue um canal pelo convite.

Catálogo

  • GET/catalog

    Catálogo da conta Business.

  • GET/catalog/products

    Produtos do catálogo.

  • GET/catalog/products/{productId}

    Um produto.

Chamadas

  • POST/calls/{callId}/reject

    Recusa uma chamada tocando.

  • POST/calls/link

    Gera link de chamada.

Modelos de texto

  • GET/templates

    Modelos de texto salvos.

  • POST/templates

    Cria modelo.

  • DELETE/templates/{id}

    Apaga modelo.

  • GET/templates/{id}

    Um modelo.

  • PUT/templates/{id}

    Edita modelo.

Respostas automáticas

  • GET/automation-rules

    Regras de resposta automática.

  • POST/automation-rules

    Cria regra de resposta automática.

  • DELETE/automation-rules/{ruleId}

    Apaga regra.

  • GET/automation-rules/{ruleId}

    Uma regra.

  • PUT/automation-rules/{ruleId}

    Edita regra.

Mídia

  • GET/media/convert

    A conversão de mídia no servidor está ligada?

  • POST/media/convert/video

    Converte vídeo em MP4 que o WhatsApp toca.

  • POST/media/convert/voice

    Converte áudio em mensagem de voz (OGG/Opus).

Webhooks

  • GET/webhooks

    Webhooks da instância.

  • POST/webhooks

    Cria webhook.

  • DELETE/webhooks/{id}

    Apaga webhook.

  • GET/webhooks/{id}

    Um webhook.

  • PUT/webhooks/{id}

    Edita webhook.

  • POST/webhooks/{id}/test

    Manda um evento de teste.

Ainda sem instância? Crie sua conta e conecte o número em poucos minutos.