ConnectaWhats Documentação da API
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.

ItemValor
URL basehttps://connecta-api.riapp.app
FormatoJSON (Content-Type: application/json), exceto uploads (multipart/form-data)
AutenticaçãoHeader Authorization: Bearer <token> (ver Autenticação)
Servidor MCPhttps://connecta-api.riapp.app/mcp
Fuso das métricasAmerica/Sao_Paulo

Conceitos

TermoO que é
TenantUma 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).
WABAA conta do WhatsApp Business na Meta. Templates e Flows pertencem à WABA, e valem para todos os números dela.
ContatoQuem conversa com um número. Guarda nome, telefone (waId), BSUID (userId), estágio do lead, origem e modo humano.
wamidId da mensagem na Meta (wamid.HBg...). Usado em mark-read, reações e citações (replyTo).
BSUIDId 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"?: [...] }. issues explica o que falta num template; details traz os campos inválidos.
  • 401 token ausente ou inválido · 403 chave sem a permissão necessária · 404 recurso inexistente ou de outro usuário (o sistema não revela recursos alheios) · 422 recurso existe mas não pode ser usado assim · 502 a Meta recusou: metaCode e metaMessage trazem 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ãoLibera
readTodas as consultas (GET): tenants, números, contatos, mensagens, mídias, templates, campanhas, Flows, logs, métricas e custos. Também conectar no MCP.
sendEnvio 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.
templatesCriar, 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 404 em 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

bash
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

bash
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?" }'
resposta
{ "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.webhookScope diz 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:

FiltroEventosPadrão
Mensagens recebidasmessage, message_echo, history_sync, contacts_syncligado
Status de mensagemstatusdesligado
Alertas de contaaccount, templatedesligado

Headers, assinatura e tentativas

HeaderConteúdo
X-WA-Bridge-SignatureHMAC-SHA256 em hexadecimal do corpo bruto, usando o secret do destino (tenant ou número).
AuthorizationBearer <secret> — alternativa simples à assinatura (ex.: Header Auth no n8n).
X-WA-Bridge-Eventmessage · status · message_echo · history_sync · contacts_sync · account · template
X-WA-Bridge-AttemptNú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.
validar a assinatura (Node.js / Express)
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

json
{
  "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"
  }
}
CampoUso
textResumo legível de qualquer tipo ("[mensagem de voz]", "📍 Av. Paulista", título do botão clicado). Use como entrada de texto para a IA.
humanModeQuando true, um atendente assumiu a conversa: a automação não deve responder.
leadStageFRIO, QUENTE, MANUAL_FRIO ou MANUAL_QUENTE.
fromQuem 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.userIdTelefone e BSUID do cliente, quando conhecidos. A mesma pessoa é um contato só, pelos dois.
message.idwamid → messageId no mark-read e replyTo para citar.
wa_bridge.tenantId / phoneNumberIdIds para montar a URL de resposta.
wa_bridge.messageIdId interno da mensagem (para baixar a mídia depois).

Campos extras, presentes quando se aplicam:

CampoQuandoConteúdo
mediaimagem, áudio, vídeo, documento, figurinha{ type, url, expiresAt, mimeType, size, sha256, filename, caption, voice, downloadEndpoint }
replyclique em botão, lista, botão de template ou Flow{ kind: "button" | "list" | "template_button" | "flow", id?, title?, payload?, response? }
replyTocliente citou uma mensagemwamid da mensagem citada
reactionreação{ messageId, emoji } — emoji vazio = reação removida
referrallead de anúncio Click-to-WhatsApp{ source_id, source_type, source_url, headline, body, media_type, ctwa_clid, ... }
forwardedmensagem encaminhada{ forwarded, frequently }
editcliente editou uma mensagem{ originalMessageId, body }
revokedMessageIdcliente apagou uma mensagemwamid apagado
flowresposta de WhatsApp Flowver abaixo

Mídia recebida

O arquivo é baixado da Meta na hora e guardado no storage. O evento traz um link pronto:

json
"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.url baixa direto, sem header nenhum, por 24h.
  • Depois disso, chame GET downloadEndpoint com 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

json
{
  "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

json
{ "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)

EventoQuandoConteúdo
message_echoA empresa enviou uma mensagem pelo app WhatsApp Business do celularIgual a message, com origin: "business_app", message.to e sentAt. contact é o cliente.
history_syncUma 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_syncAo conectar e quando a agenda do app mudacontacts: [{ 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:

json
"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

json
{
  "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.

Para responder, use o campo 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ó.
POST/api/tenants/:tenantId/phones/:phoneId/sendpermissão: send

Envia qualquer tipo de mensagem. Resposta: { "success": true, "wamid": "wamid...", "message": { ... } }.

typeCampos
texttext (até 4096), previewUrl?
image video audio document stickerlink (URL pública) ou id (de POST .../media), caption? (image/video/document), filename? (document)
locationlatitude, longitude, name?, address?
contactscontacts: [{ name: { formatted_name }, phones?: [{ phone, wa_id? }], emails?, org? }]
reactionmessageId (wamid), emoji ("" remove)
buttonsbody, buttons: [{ id, title }] (máx. 3, título até 20), header?, footer?
listbody, buttonText, sections: [{ title?, rows: [{ id, title, description? }] }] (máx. 10 itens), header?, footer?
cta_urlbody, buttonText (até 20), url, header?, footer?
location_requestbody — pede a localização do cliente
carouselbody, cards (2 a 10): [{ header: { type: "image" | "video", link }, body?, url?: { text, url } | quickReplies?: [{ id, title }] }]
flowflowId (id do Flow no ConnectaWhats), body, buttonText (até 30), header?, footer?, data?
templatetemplate: { 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" } }.

exemplos de corpo
{ "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)

POST/api/tenants/:tenantId/phones/:phoneId/send-templatepermissão: send
json
{
  "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" }]
  }
}
CampoFormato
header{type:"text", text} · {type:"image"|"video", link|id} · {type:"document", link|id, filename?} · {type:"location", latitude, longitude, name?, address?}
bodyLista 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}
carouselUm 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 é 400 com issues explicando (ex.: “botão 0 exige o sufixo da URL”).
  • Formato legado também aceito: { "to", "templateName", "languageCode", "parameters": ["v1", "v2"] }.

Upload de mídia

POST/api/tenants/:tenantId/phones/:phoneId/mediapermissão: send

multipart/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.

bash
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…”

POST/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)

RotaCorpo
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.

RotaCorpo
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

GET/api/tenants/:tenantId/phones/:phoneId/templatespermissão: read

Templates da WABA do número. Por padrão só os aprovados; ?status=ALL traz todos e ?refresh=1 sincroniza com a Meta antes.

GET/api/tenants/:tenantId/phones/:phoneId/templates/requirements?name=pedido_enviado&language=pt_BRpermissão: read

Diz 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.

GET/api/templates?search=&status=permissão: read

Templates de todas as suas WABAs, com os números que podem usá-los (owners).

POST/api/tenants/:tenantId/phones/:phoneId/templatespermissão: templates

Cria 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.

json
{
  "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" }] }
  ]
}
POST/api/tenants/:tenantId/phones/:phoneId/templates/:templateIdpermissão: templates

Edita components e/ou category. templateId é o id do template na Meta.

DELETE/api/tenants/:tenantId/phones/:phoneId/templates/:templateIdpermissão: templates
POST/api/tenants/:tenantId/phones/:phoneId/templates/syncpermissão: templates

Força a sincronização com a Meta. Devolve { "total": n }.

POST/api/tenants/:tenantId/phones/:phoneId/templates/samplepermissão: templates

Upload (multipart, campo file) da mídia de exemplo do cabeçalho. Devolve o handle para example.header_handle.

POST/api/templates/copypermissão: templates

Copia um template de qualquer WABA sua para outro número, inclusive a mídia de exemplo:

json
{ "sourceTemplateId": "123456789", "targetTenantId": "clx...", "targetPhoneId": "clx...", "newName": "promo_v2" }

Consultar

Tenants e números

GET/api/tenantspermissão: read

Seus tenants, com contagem de números e mensagens. Com chave restrita, só o tenant dela.

GET/api/tenants/:idpermissão: read

Um tenant com os números dele.

GET/api/tenants/:tenantId/phonespermissão: read

Números do tenant: id, phoneNumber, displayName, metaPhoneId, wabaId, status, qualidade, coexistência e webhook próprio. O token da Meta nunca é devolvido.

GET/api/tenants/:tenantId/phones/:phoneId/statuspermissão: read
GET/api/wabas?tenantId=permissão: read

Contas 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/profilePerfil comercial: descrição, endereço, e-mail, sites, categoria, foto.
/api/tenants/:tenantId/phones/:phoneId/conversationalMensagem de boas-vindas, sugestões de início e comandos "/".
/api/tenants/:tenantId/phones/:phoneId/qr-codesQR Codes / links wa.me com a mensagem pronta e a origem de lead ligada.
/api/tenants/:tenantId/phones/:phoneId/blockedUsuários bloqueados (paginação com ?after=).
/api/tenants/:tenantId/phones/:phoneId/official-accountStatus do selo azul (conta oficial).

Consultar

Contatos e conversas

GET/api/tenants/:tenantId/contactspermissão: read

Filtros na query string:

ParâmetroDescrição
nameBusca no nome (sem diferenciar maiúsculas).
phoneNumberParte do telefone.
leadStageFRIO, QUENTE, MANUAL_FRIO ou MANUAL_QUENTE.
humanModetrue / false.
leadSourceIdContatos de uma origem.
sortBy / sortDirfirstSeenAt, lastSeenAt (padrão), messageCount, name · asc / desc (padrão).
limit / offsetAté 100 por página (padrão 20).

Resposta: { "total", "contacts": [...], "limit", "offset" }.

GET/api/tenants/:tenantId/contacts/:contactIdpermissão: read

Um contato com a origem e as 20 mensagens mais recentes.

PATCH/api/tenants/:tenantId/contacts/:contactIdpermissão: send

Atualiza name, leadStage, leadNote e humanMode. Ligar humanMode sinaliza à automação que um atendente assumiu.

json
{ "humanMode": true, "leadStage": "MANUAL_QUENTE", "leadNote": "Pediu orçamento para 150 convidados" }
GET/api/tenants/:tenantId/contacts/export?from=&to=&stage=HOT|COLDpermissão: read

Exporta os contatos em CSV, com o funil.

GET/api/tenants/:tenantId/lead-sourcespermissão: read

Origens de lead (anúncios, QR Codes, palavras-chave) com a contagem de contatos de cada uma.

Mensagens

GET/api/tenants/:tenantId/messagespermissão: read

Filtros: phoneNumberId, from (parte do telefone), type, forwardStatus (pending, delivered, failed, filtered, skipped), limit (até 100), offset. Mais recentes primeiro.

GET/api/tenants/:tenantId/messages/:messageIdpermissão: read

Uma mensagem com o payload original e as tentativas de entrega ao seu webhook. messageId é o id interno.

GET/api/tenants/:tenantId/messages/:messageId/mediapermissão: read

Redireciona (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.

GET/api/campaigns?tenantId=&limit=&offset=permissão: read

Campanhas com status e progresso (enviados, erros).

GET/api/campaigns/:idpermissão: read

Uma campanha com os primeiros 100 destinatários e o status de cada envio.

GET/api/flows?wabaId=&sync=1permissão: read

WhatsApp Flows (formulários) com status (DRAFT, PUBLISHED, DEPRECATED…), campos e total de respostas. sync=1 (com wabaId) atualiza o status na Meta.

GET/api/flows/:idpermissão: read
GET/api/flows/:id/responses?limit=permissão: read

Respostas 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.

POST/api/tenants/:tenantId/contacts/:contactId/conversionspermissão: send
json
{ "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 em Purchase. reference (nº do pedido) evita enviar a mesma venda duas vezes. QualifiedLead e LeadSubmitted sã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.
GET/api/tenants/:tenantId/conversions?contactId=&limit=permissão: read

Conversõ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âmetroExemploObservação
tenantIdclx...Sem filtro = a conta inteira (ou o tenant da chave restrita).
phoneIdclx... ou 1234567890Id interno ou phone_number_id da Meta.
periodtoday, 24h, 7d, 30d, 90dPadrão 7d.
from / to2026-11-01 ou 2026-11-01T00:00:00-03:00ISO 8601; têm precedência sobre period. Máximo de 400 dias.
Rota (GET, permissão read)Retorna
/api/metrics/overviewRecebidas, 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|dayRecebidas, enviadas e falhas por hora ou dia.
/api/metrics/errors?limit=20Falhas de envio por código de erro da Meta (com explicação) e as mais recentes.
/api/metrics/webhook-failuresEntregas ao seu webhook que falharam: status HTTP, erro, destino.
/api/metrics/contactsContatos 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

GET/api/tenants/:tenantId/webhook-logs?success=&phoneNumberId=&eventType=&limit=&offset=permissão: read

Cada tentativa de entrega ao seu webhook: URL, status HTTP, tentativa, corpo enviado e resposta.

GET/api/audit-logs?tenantId=&success=true|false|filtered&from=&to=permissão: read

O mesmo para todos os tenants. filtered = eventos que não foram enviados por causa dos filtros do tenant.

exemplo
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.

ItemValor
Endpointhttps://connecta-api.riapp.app/mcp
TransporteStreamable HTTP, sem sessão (só POST; GET e DELETE devolvem 405)
AutenticaçãoAuthorization: Bearer cwk_... — chave de API
PermissõesConectar 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

FerramentaPermissãoParâmetrosO que faz
list_tenantsread—Tenants e números da conta. Ponto de partida para descobrir os ids.
list_conversationsreadtenantId, limit?, search?Contatos com atividade recente, estágio do lead e modo humano.
get_messagesreadtenantId, contact (telefone ou BSUID), phoneId?, limit?Histórico de uma conversa, da mais antiga para a mais recente.
get_media_urlreadtenantId, messageIdLink temporário (1h) para baixar áudio, imagem ou documento.
list_templatesreadtenantId, phoneId, status?, refresh?Templates da WABA do número.
get_template_requirementsreadtenantId, phoneId, name, language?O que o envio de um template exige.
list_campaignsreadtenantId?Campanhas e progresso.
list_flowsreadwabaId?WhatsApp Flows (formulários), status e total de respostas.
get_flow_responsesreadflowId, limit?Respostas de um Flow em texto legível.
list_qr_codesreadtenantId, phoneIdQR Codes / links wa.me e quantos contatos chegaram por cada um.
get_metrics_overviewreadtenantId?, phoneId?, period?, from?, to?Volume, contatos únicos, entrega e leitura, por tipo, tenant e número.
get_metrics_timeseriesread+ granularity? (hour|day)Recebidas, enviadas e falhas ao longo do tempo.
get_metrics_errorsread+ limit?Falhas de envio por código da Meta, com explicação.
get_webhook_failuresread+ limit?Entregas ao seu webhook que falharam.
get_contact_metricsreadtenantId?, phoneId?, period?, from?, to?Novos e ativos, funil, origens, modo humano.
get_meta_costsreadtenantId?, phoneId?, period?, from?, to?Mensagens cobradas pela Meta por categoria, dia e número.
get_template_performancereadwabaId, templateIds?, period?Envios, leituras, cliques e custo por template.
send_textsendtenantId, phoneId, to (telefone ou BSUID), text, replyTo?Envia texto (janela de 24h).
send_messagesendtenantId, phoneId, messageEnvia qualquer tipo; message segue o corpo de POST .../send.
send_templatesendtenantId, phoneId, to (telefone ou BSUID), templateEnvia template aprovado (valida e explica o que faltar).
mark_as_readsendtenantId, phoneId, wamid, typing?Marca como lida e mostra “digitando…”.
update_contactsendtenantId, contactId, humanMode?, leadStage?, leadNote?Liga/desliga modo humano e muda o estágio do lead.
send_conversion_eventsendtenantId, contactId, eventName, value?, currency?, reference?Envia uma conversão de lead de anúncio para a Meta.
create_templatetemplatestenantId, phoneId, name, language, category, components, parameterFormat?Cria um template (vai para aprovação).
copy_templatetemplatessourceTemplateId, targetTenantId, targetPhoneId, newName?, language?Copia um template para outro número/WABA.
  • period: today, 24h, 7d (padrão), 30d, 90d. from/to em ISO 8601 têm precedência.
  • Ferramentas com phoneId aceitam o id interno; as de métricas aceitam também o phone_number_id da Meta.
  • Erros voltam como resultado da ferramenta com isError: true e 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 chave cwk_....
  • Tools to Include: Selected e marque só as que o agente precisa (ex.: get_messages, send_message, update_contact). Menos ferramentas = escolhas mais certeiras do modelo.
exemplo de system prompt do agente
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 Authorization com Header Auth.
  • IF {{ $json.humanMode }} é true → encerra (um atendente está na conversa).
  • HTTP Request POST .../mark-read com typing: 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, valor Bearer cwk_... (ou o secret do tenant).
  • Destinatário: "to": "{{ $json.from }}" no corpo. O from traz 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

bash
claude mcp add --transport http connectawhats https://connecta-api.riapp.app/mcp \
  --header "Authorization: Bearer cwk_SUA_CHAVE"

# conferir
claude mcp list

Claude 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):

claude_desktop_config.json
{
  "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

~/.cursor/mcp.json (ou .cursor/mcp.json no projeto)
{
  "mcpServers": {
    "connectawhats": {
      "url": "https://connecta-api.riapp.app/mcp",
      "headers": { "Authorization": "Bearer cwk_SUA_CHAVE" }
    }
  }
}

VS Code (GitHub Copilot)

.vscode/mcp.json
{
  "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.

bash
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)

trecho de tools
"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:

bash
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?”