Documentação
Como conectar um número de WhatsApp e começar a enviar e receber mensagens pela API.
Início rápido
- Crie sua conta em /app (ou via
POST /api/v1/signup) e guarde a API key — ela só aparece uma vez. - Crie uma instância: escolha o modo qr (WhatsApp normal, via QR Code) ou official (WhatsApp Business Cloud API, da Meta).
- Modo QR: chame
connecte escaneie o código com o app do WhatsApp. Modo oficial: complete o Embedded Signup da Meta. - (Opcional) Configure um webhook para receber mensagens e eventos em tempo real.
- Envie sua primeira mensagem com
POST /api/v1/message/text.
# 1. criar conta
curl -X POST https://SEU_DOMINIO/api/v1/signup \
-H 'Content-Type: application/json' \
-d '{"name":"Minha Empresa"}'
# 2. criar instância (modo qr)
curl -X POST https://SEU_DOMINIO/api/v1/instance/create \
-H 'X-API-Key: SUA_API_KEY' -H 'Content-Type: application/json' \
-d '{"id":"vendas","name":"Time de Vendas","connectionType":"qr"}'
# 3. gerar QR (repita a cada ~15s até parear)
curl -X POST https://SEU_DOMINIO/api/v1/instance/vendas/connect \
-H 'X-API-Key: SUA_API_KEY'
# 5. enviar mensagem
curl -X POST https://SEU_DOMINIO/api/v1/message/text \
-H 'X-API-Key: SUA_API_KEY' -H 'Content-Type: application/json' \
-d '{"instanceId":"vendas","to":"5511999999999","text":"Olá!"}'
POST /instance/:id/connect cria a instância sozinho na primeira chamada se :id
ainda não existir (use o id do cliente no seu sistema como :id, e ?name= pra dar um nome).
Uma chamada só, mesmo fluxo pra criar e pra reconectar depois.
Autenticação
Toda rota em /api/v1/* (exceto /signup) exige o header X-API-Key com a chave da sua conta. A chave é gerada uma única vez, na criação da conta — o servidor guarda só o hash dela, então se você perder, precisa criar uma conta nova.
QR vs Oficial — qual escolher
| QR (whatsmeow) | Oficial (Meta Cloud API) | |
|---|---|---|
| Como conecta | Escaneando um QR Code, como o WhatsApp Web | Embedded Signup da Meta (OAuth) |
| Número | Seu número de WhatsApp normal | Número cadastrado como WhatsApp Business |
| Templates aprovados | Não suportado | Suportado (/message/template) |
| Risco de bloqueio | Existe — por isso o aquecimento gradual e o proxy dedicado | Nenhum (canal oficial da Meta) |
Use qr para começar rápido com um número existente. Use official quando precisar de templates aprovados ou volume alto sem risco de bloqueio.
Proxy dedicado por instância
Toda instância no modo qr sai da plataforma por um IP próprio e fixo — nunca compartilhado com outras instâncias, suas ou de outros clientes. Isso existe porque o WhatsApp associa reputação de número também ao IP de origem: se um número for bloqueado ou sinalizado, um IP compartilhado manchava a reputação de todos os outros números que saíssem por ele.
Você pode ver o IP (host:porta, sem credenciais) atribuído a cada instância no painel ou no campo proxyHost de GET /api/v1/instance. Ele é sorteado uma vez, na criação da instância, e não muda depois — trocar o IP de saída de uma sessão já pareada é, por si só, um sinal suspeito para o WhatsApp.
Webhooks
Registre uma URL por instância para receber eventos em tempo real via POST /api/v1/webhook/set. Sem a lista events, você recebe todos os tipos. O corpo enviado ao seu endpoint é sempre:
{
"event": "message.received",
"instance": "vendas",
"data": { ... },
"timestamp": 1234567890
}
| Evento | Quando dispara |
|---|---|
message.received | Mensagem recebida de um contato |
message.delivered / message.read | Confirmação de entrega/leitura de uma mensagem sua |
message.retry | Destinatário pediu reenvio (sessão dessincronizada) |
call.rejected | Chamada recebida e rejeitada automaticamente (setting rejectCalls) |
connection.update | Instância conectou, desconectou ou saiu (logout) |
instance.throttled | Envios pausados automaticamente — reason: retry_rate_exceeded (sessão instável) ou low_delivery_rate (mensagens aceitas mas não entregues) |
contact.opted_out | Um contato respondeu "parar"/"sair"/etc — sai dos próximos disparos em massa automaticamente |
bulk.batch_pause | Disparo em massa pausou entre lotes (ritmo anti-bloqueio) |
bulk.completed | Disparo em massa terminou a lista inteira |
bulk.stopped | Disparo em massa parou antes de terminar (limite diário, circuit breaker ou cancelado via /message/bulk/stop) |
Uma entrega falha é reenviada até 3 vezes com backoff; depois disso é descartada — não há fila de retentativa persistente, então seu endpoint deve responder rápido e com 2xx.
Disparo em massa
POST /api/v1/message/bulk enfileira o envio em background (a resposta HTTP volta na hora, antes de qualquer mensagem sair) e aplica várias camadas anti-bloqueio automaticamente. Acompanhe o progresso com GET /message/bulk/status/:instanceId e cancele com POST /message/bulk/stop/:instanceId — o cancelamento vale na próxima pausa, não é instantâneo.
O que roda automaticamente
- Delay aleatório entre mensagens (
minDelay/maxDelay, segundos) e pausa entre lotes (batchSizemensagens, depoisbatchPauseSecde descanso). - Janela de horário e teto por mensagens/hora — configurados por instância em
/dispatch/settings, não por request. - "Digitando..." antes de cada mensagem, proporcional ao tamanho do texto (só modo qr).
- Checagem de WhatsApp — números sem WhatsApp são pulados antes de começar (só modo qr; volta em
skippedNotOnWA). - Opt-out automático — quem já respondeu "parar"/"sair" é filtrado antes de começar (volta em
skippedOptOut). Veja/dispatch/optouts. - Circuit breaker — pausa a instância sozinha (retorna
429pro resto do lote) se detectar excesso de retry receipts ou taxa de entrega baixa.
Qualquer campo de ritmo que a requisição não mandar usa a configuração salva da instância (/dispatch/settings), que por sua vez cai em defaults conservadores se a instância nunca configurou nada: 8–20s de delay, lotes de 20 com 5min de pausa, janela 8h–21h (America/Sao_Paulo), 40 msgs/hora.
Personalização: variáveis e spintax
O texto (em text ou em cada item de variants) aceita:
{{chave}}— substituída pelovarsdaquele destinatário (só funciona comrecipients, não comnumbers). Sem valor pra chave, o placeholder some do texto em vez de vazar literal.{opção1|opção2|opção3}— sorteada a cada mensagem, em qualquer um dos dois modos.
variants, quando presente, sorteia uma mensagem inteira por destinatário — combine com {{chave}}/spintax pra nunca repetir o texto exato. Mandar a mesma mensagem, idêntica, pra centenas de números é um dos sinais mais fortes de bloqueio do WhatsApp.
curl -X POST https://SEU_DOMINIO/api/v1/message/bulk \
-H 'X-API-Key: SUA_API_KEY' -H 'Content-Type: application/json' \
-d '{
"instanceId": "vendas",
"recipients": [
{"number": "5511999999999", "vars": {"nome": "Ana"}},
{"number": "5511888888888", "vars": {"nome": "Carlos"}}
],
"text": "Oi {{nome}}! {Tudo bem?|Como vai?} Passando pra avisar da promoção."
}'
# acompanhar
curl https://SEU_DOMINIO/api/v1/message/bulk/status/vendas -H 'X-API-Key: SUA_API_KEY'
# cancelar
curl -X POST https://SEU_DOMINIO/api/v1/message/bulk/stop/vendas -H 'X-API-Key: SUA_API_KEY'
Alternativa mais simples, sem personalização por destinatário: numbers: ["5511999999999", ...] no lugar de recipients, e/ou variants: ["texto A", "texto B"] no lugar de text.
/messages/templates) guardam um conjunto de variantes reaproveitável entre disparos — crie uma vez, referencie o array variants devolvido em requisições futuras.Conectar com Claude (MCP)
O projeto inclui um servidor MCP (mcp-server/) que expõe a API inteira como tools pro Claude Code ou Claude Desktop — criar/conectar instância (QR Code aparece direto na conversa), enviar mensagem, disparo em massa com as proteções anti-bloqueio, templates, webhooks, opt-outs e settings, tudo por conversa em vez de curl.
cd mcp-server
npm install
claude mcp add whatsapp-api \
--env WHATSAPP_API_URL=http://localhost:8081 \
--env WHATSAPP_API_KEY=SUA_API_KEY \
-- node ./index.js
Detalhes completos (config pro Claude Desktop, lista de tools, variáveis de ambiente) em mcp-server/README.md.
Referência de endpoints
Conta
| POST | /api/v1/signup | Cria sua conta. Sem autenticação. |
| GET | /api/v1/me | Dados da conta autenticada |
Instâncias
| POST | /api/v1/instance/create | {id, name, connectionType} |
| GET | /api/v1/instance | Lista as instâncias da conta |
| GET | /api/v1/instance/:id/status | Status, telefone, proxyHost |
| POST | /api/v1/instance/:id/connect?name= | Gera/renova o QR (só modo qr). Cria a instância automaticamente se :id ainda não existir — não precisa chamar /instance/create antes |
| POST | /api/v1/instance/:id/connect-official | {code, phoneNumberId?} — completa o Embedded Signup |
| GET | /api/v1/instance/:id/wabas?code= | Lista WABAs disponíveis antes de confirmar |
| POST | /api/v1/instance/:id/logout | Encerra a sessão |
| DELETE | /api/v1/instance/:id | Remove a instância |
Mensagens
| POST | /api/v1/message/text | {instanceId, to, text} |
| POST | /api/v1/message/template | {instanceId, to, template, lang, components} — só modo oficial |
| POST | /api/v1/message/bulk | {instanceId, numbers[] ou recipients[], text ou variants[], minDelay?, maxDelay?, batchSize?, batchPauseSec?} — ver Disparo em massa |
| GET | /api/v1/message/bulk/status/:instanceId | Progresso do disparo em andamento (ou {"status":"idle"}) |
| POST | /api/v1/message/bulk/stop/:instanceId | Cancela o disparo em andamento |
Configuração de disparo
| GET | /api/v1/dispatch/settings/:instanceId | Ritmo anti-bloqueio salvo (defaults se nunca configurado) |
| POST | /api/v1/dispatch/settings/set | {instanceId, minDelaySec, maxDelaySec, batchSize, batchPauseSec, activeHourStart, activeHourEnd, timezone, maxPerHour} |
Templates de mensagem
| POST | /api/v1/messages/templates | {instanceId, name, variants[]} — cria um conjunto reaproveitável de variantes |
| GET | /api/v1/messages/templates/:instanceId | Lista os templates da instância |
| DELETE | /api/v1/messages/templates/:instanceId/:id | Remove um template |
Opt-outs
| GET | /api/v1/dispatch/optouts/:instanceId | Números que pediram pra sair dos disparos em massa |
| DELETE | /api/v1/dispatch/optouts/:instanceId/:phone | Reinclui um contato manualmente |
Webhooks
| POST | /api/v1/webhook/set | {instanceId, url, events[]} |
| GET | /api/v1/webhook/:instanceId | Webhook atual da instância |
| DELETE | /api/v1/webhook/:instanceId | Remove o webhook |
Configurações da instância (só modo qr)
| POST | /api/v1/settings/set | {instanceId, rejectCalls, ignoreGroups, alwaysOnline, readMessages, syncFullHistory, readStatus} |
| GET | /api/v1/settings/:instanceId | Toggles atuais da instância |
Rate limits
Rotas gerais (/api/v1/*) | 100 requisições/minuto por API key |
/api/v1/message/bulk | 5 requisições/minuto por API key |
/api/v1/signup | 5 requisições/hora por IP |
Além do daily warmup: instâncias no modo qr têm um teto de mensagens por dia conforme a idade da conexão (começa em 50/dia e sobe com o tempo) — envios acima do teto retornam 429.
Erros comuns
401 | API key ausente |
403 | API key inválida |
404 | Instância não encontrada (ou pertence a outra conta) |
409 | Já existe uma instância com esse id |
429 | Rate limit, limite diário de envio, ou instância pausada pelo circuit breaker (retries em excesso ou taxa de entrega baixa) |
Toda resposta de erro tem o formato {"error": "mensagem"}.