Nesta página
Documentação da API
Tudo para integrar sistemas, automações e agentes de IA ao ConnectaWhats: autenticação, eventos recebidos, envio de mensagens, consultas, métricas e o servidor MCP.
Começo
Introdução
O ConnectaWhats fica entre a WhatsApp Cloud API da Meta e o seu sistema (n8n, CRM, backend próprio ou agente de IA). Ele recebe os eventos da Meta e entrega no seu webhook, já normalizados e assinados. Na outra direção, você envia mensagens por uma API REST simples ou pelo servidor MCP, sem lidar com tokens da Meta.
| Item | Valor |
|---|---|
| URL base | https://connecta-api.riapp.app |
| Formato | JSON (Content-Type: application/json), exceto uploads (multipart/form-data) |
| Autenticação | Header Authorization: Bearer <token> (ver Autenticação) |
| Servidor MCP | https://connecta-api.riapp.app/mcp |
| Fuso das métricas | America/Sao_Paulo |
Conceitos
| Termo | O que é |
|---|---|
| Tenant | Uma conta/cliente dentro da sua conta. Agrupa números, contatos, mensagens e o webhook padrão. O id aparece como tenantId. |
| Número (phone) | Um número de WhatsApp conectado a um tenant. Nas URLs, phoneId é o id interno (não o phone_number_id da Meta, que aparece como metaPhoneId). |
| WABA | A conta do WhatsApp Business na Meta. Templates e Flows pertencem à WABA, e valem para todos os números dela. |
| Contato | Quem conversa com um número. Guarda nome, telefone (waId), BSUID (userId), estágio do lead, origem e modo humano. |
| wamid | Id da mensagem na Meta (wamid.HBg...). Usado em mark-read, reações e citações (replyTo). |
| BSUID | Id do usuário na Meta (ex.: BR.1234567890), usado quando o usuário tem nome de usuário e não expõe o telefone. |
Respostas e erros
- Sucesso nos envios:
{ "success": true, ... }. Listagens:{ "data": [...], "total": n }. - Erro: status 4xx/5xx com
{ "error": "mensagem", "detail"?: ..., "details"?: ..., "issues"?: [...] }.issuesexplica o que falta num template;detailstraz os campos inválidos. 401token ausente ou inválido ·403chave sem a permissão necessária ·404recurso inexistente ou de outro usuário (o sistema não revela recursos alheios) ·422recurso existe mas não pode ser usado assim ·502a Meta recusou:metaCodeemetaMessagetrazem o código e a mensagem dela.
Começo
Autenticação
Toda chamada leva Authorization: Bearer <token>. Existem dois tipos de token:
1. Chave de API (recomendado)
Criada em Meu perfil → Chaves de API. Começa com cwk_ e é mostrada uma única vez — guarde num cofre de segredos. Cada chave tem permissões e pode ficar restrita a um único tenant. Revogar a chave corta o acesso na hora.
| Permissão | Libera |
|---|---|
read | Todas as consultas (GET): tenants, números, contatos, mensagens, mídias, templates, campanhas, Flows, logs, métricas e custos. Também conectar no MCP. |
send | Envio de mensagens (send, send-text, send-template, send-media, send-interactive, mark-read, typing, upload de mídia), atualizar contato (modo humano, estágio) e enviar conversões. |
templates | Criar, editar, excluir, sincronizar e copiar templates. |
Chaves de API não fazem operações de conta: criar tenant, conectar número, criar campanha, criar/publicar Flow, mudar perfil do número, bloquear usuários. Essas ficam só no painel e devolvem 403 com chave.
2. Secret do tenant ou do número (só para envio)
O webhookSecret do tenant (ou o do número, quando ele tem webhook próprio ativo) também é aceito, mas apenas nas rotas de envio de /api/tenants/:tenantId/phones/:phoneId/... (send, send-text, send-media, send-interactive, send-template, mark-read, typing, media) e na leitura de templates. É o mesmo valor que o sistema usa para assinar os webhooks, então serve para o fluxo responder sem precisar de outra credencial. O secret do número vale só para aquele número.
Isolamento
- Você só enxerga os seus tenants. Um id de outro usuário devolve
404. - Uma chave restrita a um tenant recebe
404em qualquer outro, inclusive no MCP. - Dê a cada integração a menor permissão possível (ex.: um painel de BI só precisa de
read).
Começo
Primeiros passos
Do zero ao primeiro envio em três chamadas:
1. Descobrir o tenant e o número
curl https://connecta-api.riapp.app/api/tenants \
-H "Authorization: Bearer cwk_SUA_CHAVE"
curl https://connecta-api.riapp.app/api/tenants/TENANT_ID/phones \
-H "Authorization: Bearer cwk_SUA_CHAVE"Use o id do tenant e o id do número (não o metaPhoneId) nas próximas chamadas.
2. Enviar uma mensagem
curl -X POST https://connecta-api.riapp.app/api/tenants/TENANT_ID/phones/PHONE_ID/send \
-H "Authorization: Bearer cwk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{ "to": "5511999999999", "type": "text", "text": "Olá! Tudo certo?" }'{ "success": true, "wamid": "wamid.HBgN...", "message": { "id": "clx...", "status": "sent", ... } }Mensagem livre só é entregue dentro da janela de 24h desde a última mensagem do cliente. Fora dela, use um template aprovado (send-template).
3. Receber as respostas
Configure a URL do seu sistema no tenant (ou no número) e trate os eventos descritos em Receber eventos.
Receber
Receber eventos (webhooks)
O ConnectaWhats faz um POST JSON na sua URL a cada evento. Você não precisa configurar nada na Meta: o callback dela aponta sempre para o ConnectaWhats, que repassa para você.
Para onde vão os eventos
- Webhook do tenant (padrão): URL e secret definidos na tela do tenant. Recebe os eventos de todos os números.
- Webhook do número (opcional): na aba Números → Webhook. Quando ativo, os eventos daquele número vão para a URL dele, assinados com o secret dele.
- O campo
wa_bridge.webhookScopediz qual foi usado:"tenant"ou"phone". - Eventos de conta e de template (
account/template) vão sempre para o webhook do tenant.
Filtros do tenant decidem o que é enviado:
| Filtro | Eventos | Padrão |
|---|---|---|
| Mensagens recebidas | message, message_echo, history_sync, contacts_sync | ligado |
| Status de mensagem | status | desligado |
| Alertas de conta | account, template | desligado |
Headers, assinatura e tentativas
| Header | Conteúdo |
|---|---|
X-WA-Bridge-Signature | HMAC-SHA256 em hexadecimal do corpo bruto, usando o secret do destino (tenant ou número). |
Authorization | Bearer <secret> — alternativa simples à assinatura (ex.: Header Auth no n8n). |
X-WA-Bridge-Event | message · status · message_echo · history_sync · contacts_sync · account · template |
X-WA-Bridge-Attempt | Número da tentativa (1 a 3). |
- Responda com status 2xx em até 10 segundos. Processamentos longos (IA, transcrição) devem rodar depois de responder.
- Se falhar, o sistema tenta mais 2 vezes (esperas de 1s e 3s). Cada tentativa fica registrada nos logs de webhook.
- A mesma mensagem pode chegar duas vezes em caso de nova tentativa: use
message.id(wamid) para não processar repetido.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
// importante: guardar o corpo BRUTO, sem reformatar o JSON
app.post('/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.CONECTAWHATS_SECRET)
.update(req.body) // Buffer com o corpo exato recebido
.digest('hex')
const received = req.get('X-WA-Bridge-Signature') ?? ''
const ok = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
if (!ok) return res.sendStatus(401)
res.sendStatus(200) // responde logo
const event = JSON.parse(req.body.toString('utf8'))
queue.push(event) // processa depois
})Mensagem recebida — message
{
"channel": "WHATSAPP",
"event": "message",
"humanMode": false,
"leadStage": "FRIO",
"leadSource": { "id": "clx...", "name": "Promo Black Friday" },
"tenant": "slug-do-tenant",
"phoneNumber": "+5511999999999",
"contact": { "name": "João", "waId": "5511988887777", "userId": "BR.1234567890", "username": null },
"from": "5511988887777",
"message": {
"id": "wamid.HBg...", "from": "5511988887777", "from_user_id": "BR.1234567890",
"type": "text", "text": { "body": "Olá" }
},
"text": "Olá",
"receivedAt": "2026-11-10T12:00:00.000Z",
"wa_bridge": {
"tenantId": "clx...",
"phoneNumberId": "clx...",
"phoneNumberMetaId": "1234567890",
"wabaId": "9876543210",
"messageId": "clx...",
"webhookScope": "tenant"
}
}| Campo | Uso |
|---|---|
text | Resumo legível de qualquer tipo ("[mensagem de voz]", "📍 Av. Paulista", título do botão clicado). Use como entrada de texto para a IA. |
humanMode | Quando true, um atendente assumiu a conversa: a automação não deve responder. |
leadStage | FRIO, QUENTE, MANUAL_FRIO ou MANUAL_QUENTE. |
from | Quem mandou: o telefone do cliente ou, se ele esconde o número (nome de usuário), o BSUID. Use como to ao responder — a API aceita os dois. Sempre vem preenchido, diferente de message.from, que some quando o telefone está oculto. |
contact.waId / contact.userId | Telefone e BSUID do cliente, quando conhecidos. A mesma pessoa é um contato só, pelos dois. |
message.id | wamid → messageId no mark-read e replyTo para citar. |
wa_bridge.tenantId / phoneNumberId | Ids para montar a URL de resposta. |
wa_bridge.messageId | Id interno da mensagem (para baixar a mídia depois). |
Campos extras, presentes quando se aplicam:
| Campo | Quando | Conteúdo |
|---|---|---|
media | imagem, áudio, vídeo, documento, figurinha | { type, url, expiresAt, mimeType, size, sha256, filename, caption, voice, downloadEndpoint } |
reply | clique em botão, lista, botão de template ou Flow | { kind: "button" | "list" | "template_button" | "flow", id?, title?, payload?, response? } |
replyTo | cliente citou uma mensagem | wamid da mensagem citada |
reaction | reação | { messageId, emoji } — emoji vazio = reação removida |
referral | lead de anúncio Click-to-WhatsApp | { source_id, source_type, source_url, headline, body, media_type, ctwa_clid, ... } |
forwarded | mensagem encaminhada | { forwarded, frequently } |
edit | cliente editou uma mensagem | { originalMessageId, body } |
revokedMessageId | cliente apagou uma mensagem | wamid apagado |
flow | resposta de WhatsApp Flow | ver abaixo |
Mídia recebida
O arquivo é baixado da Meta na hora e guardado no storage. O evento traz um link pronto:
"media": {
"type": "audio",
"url": "https://...r2.cloudflarestorage.com/...&X-Amz-Signature=...",
"expiresAt": "2026-11-11T12:00:00.000Z",
"mimeType": "audio/ogg",
"size": 17759,
"voice": true,
"downloadEndpoint": "https://connecta-api.riapp.app/api/tenants/TENANT_ID/messages/MESSAGE_ID/media"
}media.urlbaixa direto, sem header nenhum, por 24h.- Depois disso, chame
GET downloadEndpointcom o seu Bearer: ele redireciona para um link novo (ou devolve{ url }com?json=1). - Áudio de voz chega como
audio/ogg(opus). Converta se a sua API de transcrição não aceitar esse formato.
Status de entrega — status
{
"channel": "WHATSAPP",
"event": "status",
"status": { "id": "wamid...", "status": "read", "timestamp": "1762780000", "errors": [], "pricing": { ... } },
"wa_bridge": { "tenantId": "...", "phoneNumberId": "...", "messageId": "clx..." }
}status: sent → delivered → read (ou played para áudio). Em failed, veja status.errors[0] (code e message da Meta).
Conta e templates — account / template
{ "event": "message_template_status_update", "wabaId": "9876543210", "value": { "event": "APPROVED", "message_template_name": "pedido_enviado", ... } }event é o campo da Meta: message_template_status_update (aprovado/rejeitado),template_category_update, phone_number_quality_update, account_update, account_alerts.
Coexistência (número também usado no app do celular)
| Evento | Quando | Conteúdo |
|---|---|---|
message_echo | A empresa enviou uma mensagem pelo app WhatsApp Business do celular | Igual a message, com origin: "business_app", message.to e sentAt. contact é o cliente. |
history_sync | Uma vez, ao conectar o número (até 6 meses de histórico) | Lotes com progress (0–100), status (completed no fim, ou declined) e threads[]: { contact, messages: [{ id, direction, type, text, timestamp, status }] } |
contacts_sync | Ao conectar e quando a agenda do app muda | contacts: [{ action: "add" | "remove", name, waId, userId }] |
Use o message_echo para manter a conversa completa no CRM e para a IA saber que um humano respondeu pelo celular — o fluxo pode ligar o humanMode do contato. Mensagens de grupo não são sincronizadas pela Meta.
Resposta de WhatsApp Flow
Quando o cliente envia um formulário, o evento message traz o bloco flow:
"flow": {
"id": "clx...", "name": "Orçamento casamento", "metaFlowId": "1234567890",
"data": { "nome_dos_noivos": "Ana e Pedro", "data_do_casamento": "2027-03-20", "periodo": "1_tarde" },
"answers": { "Nome dos noivos": "Ana e Pedro", "Data do casamento": "2027-03-20", "Período": "Tarde" }
}answers usa os rótulos dos campos e as opções em texto — é o formato ideal para CRM e IA. data traz os nomes técnicos.
Facebook e Instagram
{
"channel": "INSTAGRAM",
"tenant": "slug-do-tenant",
"contact": { "name": null, "id": "1234567890123" },
"message": { "mid": "m_abc...", "text": "oi", "attachments": [{ "type": "image", "payload": { "url": "https://scontent..." } }] },
"receivedAt": "2026-11-10T12:00:00.000Z",
"wa_bridge": { "tenantId": "clx...", "channelId": "clx...", "replyEndpoint": "https://connecta-api.riapp.app/api/tenants/.../social-channels/.../send" }
}channel é FACEBOOK ou INSTAGRAM. As URLs de anexo já são públicas.
Enviar
Enviar mensagens (WhatsApp)
Todas as rotas ficam em /api/tenants/:tenantId/phones/:phoneId/.... O destinatário vai em to: telefone com DDI ou BSUID (ex.: BR.1234567890) — a API reconhece qual é. recipient continua aceito para o BSUID. Use replyTo (wamid) para citar uma mensagem.
from do webhook como to. Ele traz o telefone do cliente ou, quando o cliente usa nome de usuário e esconde o número, o BSUID dele. Não use só message.from: nesse caso ele não vem. O ConnectaWhats guarda os dois identificadores no mesmo contato, então o histórico continua numa conversa só./api/tenants/:tenantId/phones/:phoneId/sendpermissão: sendEnvia qualquer tipo de mensagem. Resposta: { "success": true, "wamid": "wamid...", "message": { ... } }.
| type | Campos |
|---|---|
text | text (até 4096), previewUrl? |
image video audio document sticker | link (URL pública) ou id (de POST .../media), caption? (image/video/document), filename? (document) |
location | latitude, longitude, name?, address? |
contacts | contacts: [{ name: { formatted_name }, phones?: [{ phone, wa_id? }], emails?, org? }] |
reaction | messageId (wamid), emoji ("" remove) |
buttons | body, buttons: [{ id, title }] (máx. 3, título até 20), header?, footer? |
list | body, buttonText, sections: [{ title?, rows: [{ id, title, description? }] }] (máx. 10 itens), header?, footer? |
cta_url | body, buttonText (até 20), url, header?, footer? |
location_request | body — pede a localização do cliente |
carousel | body, cards (2 a 10): [{ header: { type: "image" | "video", link }, body?, url?: { text, url } | quickReplies?: [{ id, title }] }] |
flow | flowId (id do Flow no ConnectaWhats), body, buttonText (até 30), header?, footer?, data? |
template | template: { name, language, header?, body?, buttons?, carousel?, limitedTimeOffer? } — ver templates abaixo |
header de botões/lista/cta: { "type": "text", "text": "..." } ou { "type": "image" | "video" | "document", "image" | "video" | "document": { "link" | "id" } }.
{ "to": "5511999999999", "type": "text", "text": "Segue o link", "previewUrl": true, "replyTo": "wamid.HBg..." }
{ "to": "5511999999999", "type": "document", "link": "https://exemplo.com/boleto.pdf", "filename": "boleto.pdf", "caption": "Seu boleto" }
{ "to": "BR.1234567890", "type": "buttons", "body": "Confirma o horário?",
"buttons": [{ "id": "sim", "title": "Sim" }, { "id": "nao", "title": "Não" }] }
{ "to": "5511999999999", "type": "list", "body": "Escolha o plano", "buttonText": "Ver planos",
"sections": [{ "title": "Planos", "rows": [{ "id": "basic", "title": "Básico" }, { "id": "pro", "title": "Pro", "description": "Mais usado" }] }] }
{ "to": "5511999999999", "type": "cta_url", "body": "Acompanhe seu pedido", "buttonText": "Rastrear", "url": "https://loja.com/r/123" }
{ "to": "5511999999999", "type": "flow", "flowId": "clx...", "body": "Preencha o orçamento", "buttonText": "Abrir formulário" }Use ids semânticos nos botões (confirm_yes, plan_pro): o clique volta no webhook em reply.id.
Templates (fora da janela de 24h)
/api/tenants/:tenantId/phones/:phoneId/send-templatepermissão: send{
"to": "5511999999999",
"template": {
"name": "pedido_enviado",
"language": "pt_BR",
"header": { "type": "image", "link": "https://exemplo.com/produto.jpg" },
"body": ["João", "A123"],
"buttons": [{ "type": "url", "index": 0, "text": "A123" }]
}
}| Campo | Formato |
|---|---|
header | {type:"text", text} · {type:"image"|"video", link|id} · {type:"document", link|id, filename?} · {type:"location", latitude, longitude, name?, address?} |
body | Lista posicional ({{1}}, {{2}}…) ou objeto nomeado ({"nome": "João"}). Valores especiais: {"type":"currency","fallback":"R$ 10,00","code":"BRL","amount1000":10000}, {"type":"date_time","fallback":"10/11/2026"} |
buttons | {type:"url", index, text} (sufixo da URL) · {type:"copy_code", index, couponCode} · {type:"quick_reply", index, payload} · {type:"flow", index, flowToken?, flowActionData?} · {type:"otp", index, code} |
carousel | Um item por card, na ordem: [{ header: {type, link|id}, body?: [...], buttons?: [...] }] |
limitedTimeOffer | { "expirationTimeMs": 1767225599000 } |
- Template de autenticação (OTP): basta
"body": ["123456"]; o código é repetido no botão automaticamente. - Se faltar algo, a resposta é
400comissuesexplicando (ex.: “botão 0 exige o sufixo da URL”). - Formato legado também aceito:
{ "to", "templateName", "languageCode", "parameters": ["v1", "v2"] }.
Upload de mídia
/api/tenants/:tenantId/phones/:phoneId/mediapermissão: sendmultipart/form-data com o campo file. Devolve { "id": "..." }, válido por cerca de 30 dias. Use o id em send ou no cabeçalho de templates para mandar o mesmo arquivo várias vezes sem reenviar.
curl -X POST https://connecta-api.riapp.app/api/tenants/TENANT_ID/phones/PHONE_ID/media \
-H "Authorization: Bearer cwk_SUA_CHAVE" \
-F "file=@catalogo.pdf"Marcar como lida e “digitando…”
/api/tenants/:tenantId/phones/:phoneId/mark-readpermissão: send{ "messageId": "wamid...", "typing": true } — marca como lida e mostra “digitando…” por até ~25s ou até a sua resposta. Se a IA demorar, repita a cada ~20s.
Rotas legadas (continuam funcionando)
| Rota | Corpo |
|---|---|
POST .../send-text | { "to", "message", "replyTo"? } |
POST .../send-media | { "to", "mediaType": "image|audio|video|document|sticker", "mediaUrl", "caption"?, "filename"? } |
POST .../send-interactive | { "to", "type": "button"|"list", "body", "header"?, "footer"?, "buttons" | "buttonText"+"sections" } |
POST .../typing | { "messageId" } |
Facebook e Instagram
Base: /api/tenants/:tenantId/social-channels/:channelId — o channelId vem em wa_bridge.channelId no evento.
| Rota | Corpo |
|---|---|
POST .../send | { "recipientId": "1234567890123", "text": "..." } |
POST .../send-media | { "recipientId", "mediaType": "image|audio|video|document", "mediaUrl" } |
POST .../typing | { "recipientId", "action": "typing_on" | "typing_off" } |
Enviar
Templates
/api/tenants/:tenantId/phones/:phoneId/templatespermissão: readTemplates da WABA do número. Por padrão só os aprovados; ?status=ALL traz todos e ?refresh=1 sincroniza com a Meta antes.
/api/tenants/:tenantId/phones/:phoneId/templates/requirements?name=pedido_enviado&language=pt_BRpermissão: readDiz o que o envio exige: formato e variável do cabeçalho, variáveis do corpo, botões dinâmicos, carrossel e oferta. Use antes de montar o send-template.
/api/templates?search=&status=permissão: readTemplates de todas as suas WABAs, com os números que podem usá-los (owners).
/api/tenants/:tenantId/phones/:phoneId/templatespermissão: templatesCria um template (vai para aprovação da Meta). components no formato da Meta. Em cabeçalho de mídia, example.header_handle pode ser uma URL pública.
{
"name": "lembrete_consulta",
"language": "pt_BR",
"category": "UTILITY",
"parameterFormat": "POSITIONAL",
"components": [
{ "type": "BODY", "text": "Olá {{1}}, sua consulta é amanhã às {{2}}.", "example": { "body_text": [["Ana", "14h"]] } },
{ "type": "BUTTONS", "buttons": [{ "type": "QUICK_REPLY", "text": "Confirmar" }] }
]
}/api/tenants/:tenantId/phones/:phoneId/templates/:templateIdpermissão: templatesEdita components e/ou category. templateId é o id do template na Meta.
/api/tenants/:tenantId/phones/:phoneId/templates/:templateIdpermissão: templates/api/tenants/:tenantId/phones/:phoneId/templates/syncpermissão: templatesForça a sincronização com a Meta. Devolve { "total": n }.
/api/tenants/:tenantId/phones/:phoneId/templates/samplepermissão: templatesUpload (multipart, campo file) da mídia de exemplo do cabeçalho. Devolve o handle para example.header_handle.
/api/templates/copypermissão: templatesCopia um template de qualquer WABA sua para outro número, inclusive a mídia de exemplo:
{ "sourceTemplateId": "123456789", "targetTenantId": "clx...", "targetPhoneId": "clx...", "newName": "promo_v2" }Consultar
Tenants e números
/api/tenantspermissão: readSeus tenants, com contagem de números e mensagens. Com chave restrita, só o tenant dela.
/api/tenants/:idpermissão: readUm tenant com os números dele.
/api/tenants/:tenantId/phonespermissão: readNúmeros do tenant: id, phoneNumber, displayName, metaPhoneId, wabaId, status, qualidade, coexistência e webhook próprio. O token da Meta nunca é devolvido.
/api/tenants/:tenantId/phones/:phoneId/statuspermissão: read/api/wabas?tenantId=permissão: readContas do WhatsApp (WABA) que você acessa, com os números de cada uma. Use o wabaId em Flows, custos e desempenho de templates.
Configurações do número (somente leitura pela API)
| Rota (GET, permissão read) | Retorna |
|---|---|
/api/tenants/:tenantId/phones/:phoneId/profile | Perfil comercial: descrição, endereço, e-mail, sites, categoria, foto. |
/api/tenants/:tenantId/phones/:phoneId/conversational | Mensagem de boas-vindas, sugestões de início e comandos "/". |
/api/tenants/:tenantId/phones/:phoneId/qr-codes | QR Codes / links wa.me com a mensagem pronta e a origem de lead ligada. |
/api/tenants/:tenantId/phones/:phoneId/blocked | Usuários bloqueados (paginação com ?after=). |
/api/tenants/:tenantId/phones/:phoneId/official-account | Status do selo azul (conta oficial). |
Consultar
Contatos e conversas
/api/tenants/:tenantId/contactspermissão: readFiltros na query string:
| Parâmetro | Descrição |
|---|---|
name | Busca no nome (sem diferenciar maiúsculas). |
phoneNumber | Parte do telefone. |
leadStage | FRIO, QUENTE, MANUAL_FRIO ou MANUAL_QUENTE. |
humanMode | true / false. |
leadSourceId | Contatos de uma origem. |
sortBy / sortDir | firstSeenAt, lastSeenAt (padrão), messageCount, name · asc / desc (padrão). |
limit / offset | Até 100 por página (padrão 20). |
Resposta: { "total", "contacts": [...], "limit", "offset" }.
/api/tenants/:tenantId/contacts/:contactIdpermissão: readUm contato com a origem e as 20 mensagens mais recentes.
/api/tenants/:tenantId/contacts/:contactIdpermissão: sendAtualiza name, leadStage, leadNote e humanMode. Ligar humanMode sinaliza à automação que um atendente assumiu.
{ "humanMode": true, "leadStage": "MANUAL_QUENTE", "leadNote": "Pediu orçamento para 150 convidados" }/api/tenants/:tenantId/contacts/export?from=&to=&stage=HOT|COLDpermissão: readExporta os contatos em CSV, com o funil.
/api/tenants/:tenantId/lead-sourcespermissão: readOrigens de lead (anúncios, QR Codes, palavras-chave) com a contagem de contatos de cada uma.
Mensagens
/api/tenants/:tenantId/messagespermissão: readFiltros: phoneNumberId, from (parte do telefone), type, forwardStatus (pending, delivered, failed, filtered, skipped), limit (até 100), offset. Mais recentes primeiro.
/api/tenants/:tenantId/messages/:messageIdpermissão: readUma mensagem com o payload original e as tentativas de entrega ao seu webhook. messageId é o id interno.
/api/tenants/:tenantId/messages/:messageId/mediapermissão: readRedireciona (302) para um link novo da mídia. Com ?json=1 devolve { url, mimeType, ... }. Aceita também o secret do tenant. 410 = a mídia expirou na Meta e não foi guardada.
Consultar
Campanhas e Flows
Campanhas e Flows são criados no painel. Pela API você acompanha o progresso e lê as respostas.
/api/campaigns?tenantId=&limit=&offset=permissão: readCampanhas com status e progresso (enviados, erros).
/api/campaigns/:idpermissão: readUma campanha com os primeiros 100 destinatários e o status de cada envio.
/api/flows?wabaId=&sync=1permissão: readWhatsApp Flows (formulários) com status (DRAFT, PUBLISHED, DEPRECATED…), campos e total de respostas. sync=1 (com wabaId) atualiza o status na Meta.
/api/flows/:idpermissão: read/api/flows/:id/responses?limit=permissão: readRespostas recebidas, cada uma com answers em texto legível, o contato e a data.
Para enviar um Flow publicado, use POST .../send com "type": "flow" e o flowId (veja Enviar mensagens).
Integrar
Conversões de anúncios (Conversions API)
Informa à Meta que um lead que veio de anúncio Click-to-WhatsApp avançou (lead qualificado, venda). Os anúncios passam a otimizar por resultado real.
/api/tenants/:tenantId/contacts/:contactId/conversionspermissão: send{ "eventName": "Purchase", "value": 15000, "currency": "BRL", "reference": "contrato-123" }eventName: Purchase, LeadSubmitted, QualifiedLead, InitiateCheckout, AddToCart, ViewContent, OrderCreated, OrderShipped, OrderDelivered, OrderCanceled, OrderReturned, CartAbandoned, RatingProvided, ReviewProvided.valueé obrigatório emPurchase.reference(nº do pedido) evita enviar a mesma venda duas vezes.QualifiedLeadeLeadSubmittedsão enviados uma vez por contato.- Requisitos: o contato veio de anúncio (tem
ctwaClid) e a Conversions API está ativada na WABA, no painel do tenant.
/api/tenants/:tenantId/conversions?contactId=&limit=permissão: readConversões enviadas, com o status de cada uma.
Consultar
Métricas, custos e logs
Os filtros abaixo valem para todas as rotas de métricas e custos:
| Parâmetro | Exemplo | Observação |
|---|---|---|
tenantId | clx... | Sem filtro = a conta inteira (ou o tenant da chave restrita). |
phoneId | clx... ou 1234567890 | Id interno ou phone_number_id da Meta. |
period | today, 24h, 7d, 30d, 90d | Padrão 7d. |
from / to | 2026-11-01 ou 2026-11-01T00:00:00-03:00 | ISO 8601; têm precedência sobre period. Máximo de 400 dias. |
| Rota (GET, permissão read) | Retorna |
|---|---|
/api/metrics/overview | Recebidas, enviadas, contatos únicos, status de entrega e taxas, por tipo, tenant e número, enviadas pelo celular, saúde do webhook e cobrança por categoria. |
/api/metrics/timeseries?granularity=hour|day | Recebidas, enviadas e falhas por hora ou dia. |
/api/metrics/errors?limit=20 | Falhas de envio por código de erro da Meta (com explicação) e as mais recentes. |
/api/metrics/webhook-failures | Entregas ao seu webhook que falharam: status HTTP, erro, destino. |
/api/metrics/contacts | Contatos novos e ativos, funil, origens, modo humano e quem mais conversa. |
/api/meta-insights/costs?wabaId= | Mensagens cobradas pela Meta por categoria, tipo, dia e número; custo por moeda quando a Meta informa. |
/api/meta-insights/templates?wabaId=&templateIds= | Enviados, entregues, lidos, cliques e custo por template (até 90 dias). 409 se as métricas de template não estiverem ativadas na WABA. |
Quando a WABA é paga pela linha de crédito de um parceiro, a Meta informa só o volume, sem o custo (costAvailable: false).
Logs de entrega do webhook
/api/tenants/:tenantId/webhook-logs?success=&phoneNumberId=&eventType=&limit=&offset=permissão: readCada tentativa de entrega ao seu webhook: URL, status HTTP, tentativa, corpo enviado e resposta.
/api/audit-logs?tenantId=&success=true|false|filtered&from=&to=permissão: readO mesmo para todos os tenants. filtered = eventos que não foram enviados por causa dos filtros do tenant.
curl "https://connecta-api.riapp.app/api/metrics/overview?period=30d" -H "Authorization: Bearer cwk_SUA_CHAVE"MCP
Servidor MCP
O MCP (Model Context Protocol) expõe as operações do ConnectaWhats como ferramentas que um modelo de linguagem chama sozinho. Conectado ao MCP, um agente de IA consegue ler conversas, responder clientes, consultar templates e extrair métricas da operação, sem você programar cada chamada HTTP.
| Item | Valor |
|---|---|
| Endpoint | https://connecta-api.riapp.app/mcp |
| Transporte | Streamable HTTP, sem sessão (só POST; GET e DELETE devolvem 405) |
| Autenticação | Authorization: Bearer cwk_... — chave de API |
| Permissões | Conectar exige read. Cada ferramenta confere a sua (send, templates). |
Crie uma chave só para o agente, com o mínimo de permissões e, se possível, restrita ao tenant dele. Um agente que só analisa a operação precisa apenas de read.
Ferramentas
| Ferramenta | Permissão | Parâmetros | O que faz |
|---|---|---|---|
list_tenants | read | — | Tenants e números da conta. Ponto de partida para descobrir os ids. |
list_conversations | read | tenantId, limit?, search? | Contatos com atividade recente, estágio do lead e modo humano. |
get_messages | read | tenantId, contact (telefone ou BSUID), phoneId?, limit? | Histórico de uma conversa, da mais antiga para a mais recente. |
get_media_url | read | tenantId, messageId | Link temporário (1h) para baixar áudio, imagem ou documento. |
list_templates | read | tenantId, phoneId, status?, refresh? | Templates da WABA do número. |
get_template_requirements | read | tenantId, phoneId, name, language? | O que o envio de um template exige. |
list_campaigns | read | tenantId? | Campanhas e progresso. |
list_flows | read | wabaId? | WhatsApp Flows (formulários), status e total de respostas. |
get_flow_responses | read | flowId, limit? | Respostas de um Flow em texto legível. |
list_qr_codes | read | tenantId, phoneId | QR Codes / links wa.me e quantos contatos chegaram por cada um. |
get_metrics_overview | read | tenantId?, phoneId?, period?, from?, to? | Volume, contatos únicos, entrega e leitura, por tipo, tenant e número. |
get_metrics_timeseries | read | + granularity? (hour|day) | Recebidas, enviadas e falhas ao longo do tempo. |
get_metrics_errors | read | + limit? | Falhas de envio por código da Meta, com explicação. |
get_webhook_failures | read | + limit? | Entregas ao seu webhook que falharam. |
get_contact_metrics | read | tenantId?, phoneId?, period?, from?, to? | Novos e ativos, funil, origens, modo humano. |
get_meta_costs | read | tenantId?, phoneId?, period?, from?, to? | Mensagens cobradas pela Meta por categoria, dia e número. |
get_template_performance | read | wabaId, templateIds?, period? | Envios, leituras, cliques e custo por template. |
send_text | send | tenantId, phoneId, to (telefone ou BSUID), text, replyTo? | Envia texto (janela de 24h). |
send_message | send | tenantId, phoneId, message | Envia qualquer tipo; message segue o corpo de POST .../send. |
send_template | send | tenantId, phoneId, to (telefone ou BSUID), template | Envia template aprovado (valida e explica o que faltar). |
mark_as_read | send | tenantId, phoneId, wamid, typing? | Marca como lida e mostra “digitando…”. |
update_contact | send | tenantId, contactId, humanMode?, leadStage?, leadNote? | Liga/desliga modo humano e muda o estágio do lead. |
send_conversion_event | send | tenantId, contactId, eventName, value?, currency?, reference? | Envia uma conversão de lead de anúncio para a Meta. |
create_template | templates | tenantId, phoneId, name, language, category, components, parameterFormat? | Cria um template (vai para aprovação). |
copy_template | templates | sourceTemplateId, targetTenantId, targetPhoneId, newName?, language? | Copia um template para outro número/WABA. |
period:today,24h,7d(padrão),30d,90d.from/toem ISO 8601 têm precedência.- Ferramentas com
phoneIdaceitam o id interno; as de métricas aceitam também o phone_number_id da Meta. - Erros voltam como resultado da ferramenta com
isError: truee a explicação em texto, para o modelo corrigir e tentar de novo.
MCP
Conectar no n8n
Agente de IA com as ferramentas do ConnectaWhats
- No workflow, adicione um nó AI Agent com o modelo de sua preferência.
- Em Tools, adicione o nó MCP Client Tool.
- Endpoint:
https://connecta-api.riapp.app/mcp - Server Transport:
HTTP Streamable - Authentication:
Bearer Auth, com uma credencial cujo token é a chavecwk_.... - Tools to Include:
Selectede marque só as que o agente precisa (ex.:get_messages,send_message,update_contact). Menos ferramentas = escolhas mais certeiras do modelo.
Você atende clientes da empresa pelo WhatsApp usando as ferramentas do ConnectaWhats.
- tenantId: {{ $json.wa_bridge.tenantId }}
- phoneId: {{ $json.wa_bridge.phoneNumberId }}
- cliente: {{ $json.from }} ({{ $json.contact.name }})
Antes de responder, leia as últimas mensagens com get_messages (contact = o cliente acima).
Responda com send_message (type "text", to = o cliente acima — telefone ou BSUID, use exatamente como veio). Se o cliente pedir um humano, chame
update_contact com humanMode = true e avise que um atendente vai continuar.Fluxo completo de atendimento
- Webhook (trigger) recebe o evento do ConnectaWhats. Valide o header
Authorizationcom Header Auth. - IF
{{ $json.humanMode }}é true → encerra (um atendente está na conversa). - HTTP Request
POST .../mark-readcomtyping: true(opcional, best-effort). - Se houver
media, baixe por{{ $json.media.url }}(Response Format = File) e transcreva/analise. - AI Agent com o MCP Client Tool responde e age (modo humano, estágio do lead, templates).
Sem agente: nó HTTP Request
Para chamadas fixas (ex.: enviar um template quando um pedido muda de status) use a API REST direto:
- Method POST, Body JSON.
- URL:
={{ "https://connecta-api.riapp.app/api/tenants/" + $json.wa_bridge.tenantId + "/phones/" + $json.wa_bridge.phoneNumberId + "/send" }} - Authentication: Generic → Header Auth, nome
Authorization, valorBearer cwk_...(ou o secret do tenant). - Destinatário:
"to": "{{ $json.from }}"no corpo. Ofromtraz o telefone ou o BSUID — a API aceita os dois.
MCP
Conectar em LLMs e IDEs
Serviços na nuvem (API da Anthropic, API da OpenAI) precisam alcançar o endpoint pela internet: use a URL pública do backend, não localhost.
Claude Code
claude mcp add --transport http connectawhats https://connecta-api.riapp.app/mcp \
--header "Authorization: Bearer cwk_SUA_CHAVE"
# conferir
claude mcp listClaude Desktop
Os conectores personalizados do Claude (claude.ai e Desktop) usam OAuth, que este servidor não oferece. Use a pontemcp-remote (precisa de Node.js) em claude_desktop_config.json (Configurações → Desenvolvedor → Editar config):
{
"mcpServers": {
"connectawhats": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://connecta-api.riapp.app/mcp", "--header", "Authorization:${AUTH}"],
"env": { "AUTH": "Bearer cwk_SUA_CHAVE" }
}
}
}Reinicie o Claude Desktop. As ferramentas aparecem no ícone de ferramentas da conversa.
Cursor
{
"mcpServers": {
"connectawhats": {
"url": "https://connecta-api.riapp.app/mcp",
"headers": { "Authorization": "Bearer cwk_SUA_CHAVE" }
}
}
}VS Code (GitHub Copilot)
{
"inputs": [
{ "id": "cwk", "type": "promptString", "description": "Chave de API do ConnectaWhats", "password": true }
],
"servers": {
"connectawhats": {
"type": "http",
"url": "https://connecta-api.riapp.app/mcp",
"headers": { "Authorization": "Bearer ${input:cwk}" }
}
}
}API da Anthropic (Claude)
O conector MCP da Messages API chama o servidor direto da nuvem da Anthropic. Precisa das duas partes: mcp_servers e o mcp_toolset em tools.
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4000,
"mcp_servers": [{
"type": "url",
"url": "https://connecta-api.riapp.app/mcp",
"name": "connectawhats",
"authorization_token": "cwk_SUA_CHAVE"
}],
"tools": [{ "type": "mcp_toolset", "mcp_server_name": "connectawhats" }],
"messages": [{ "role": "user", "content": "Quantas mensagens cada tenant recebeu nos últimos 7 dias e qual teve mais falhas?" }]
}'O authorization_token é enviado como Authorization: Bearer, então vai só a chave, sem o prefixo “Bearer”.
API da OpenAI (Responses)
"tools": [{
"type": "mcp",
"server_label": "connectawhats",
"server_url": "https://connecta-api.riapp.app/mcp",
"headers": { "Authorization": "Bearer cwk_SUA_CHAVE" },
"require_approval": "never",
"allowed_tools": ["get_metrics_overview", "get_metrics_errors", "get_contact_metrics"]
}]Com ferramentas de envio liberadas, prefira require_approval ligado para revisar antes de mandar mensagem a clientes.
Testar na mão (curl)
Útil para conferir a chave e a URL. O header Accept precisa aceitar JSON e SSE:
curl -X POST https://connecta-api.riapp.app/mcp \
-H "Authorization: Bearer cwk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
curl -X POST https://connecta-api.riapp.app/mcp \
-H "Authorization: Bearer cwk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_metrics_overview","arguments":{"period":"7d"}}}'Perguntas que um agente conectado responde
- “Quantas mensagens cada tenant recebeu esta semana, e qual teve mais falhas?”
- “Quais os erros de envio mais comuns nos últimos 30 dias e o que significam?”
- “Em que horário o número X recebe mais mensagens?”
- “Meu webhook está falhando? Para qual destino?”
- “Quais leads de anúncio viraram quentes este mês? Resuma a conversa de cada um.”
- “Quanto gastei com templates de marketing e qual teve a melhor taxa de leitura?”