Crialy Connect API
Documentação completa da API de WhatsApp
Introdução
O Crialy Connect é uma API RESTful completa para envio e recebimento de mensagens via WhatsApp Web. A API permite gerenciar sessões WhatsApp, enviar e receber mensagens, e gerenciar grupos programaticamente.
https://api.crialyconnect.com/functions/v1/apiTodas as requisições precisam do header X-API-Key
Autenticação
Toda requisição à API deve incluir o header X-API-Key com a chave de API do seu projeto.
Você encontra sua API Key no Dashboard Projeto API Key.
Exemplo
curl -X GET "https://api.crialyconnect.com/functions/v1/api/{project_id}/listPhones" \
-H "X-API-Key: YOUR_API_KEY"Endpoints — Projeto
Endpoints — Sessão
Números brasileiros — o problema do 9º dígito
Antes da primeira campanha em produção, leia esta seção. Evita perda de mensagens e — pior — entrega para a pessoa errada.
TL;DR: rode POST /checkPhones em todos os contatos brasileiros na importação e salve o jid retornado. Use o jid como destinatário em todos os sendMessage seguintes. Não tente adivinhar com ou sem o "9" — pergunte ao WhatsApp.
Contexto
Entre 2012 e 2016 a ANATEL adicionou um "9" ao início de todo celular brasileiro. O WhatsApp grava o JID interno do contato com o formato que ele tinha quando se registrou:
- Registrado pré-migração e nunca re-registrou →
551187654321@c.us(SEM o 9, 8 dígitos depois do DDD) - Registrado pós-migração ou trocou de chip →
5511987654321@c.us(COM o 9, 9 dígitos depois do DDD)
Mandar pro JID errado resulta em:
- erro "número não está no WhatsApp" (caso comum, fácil de detectar), OU
- mensagem entregue pra outra pessoa caso aquele JID alternativo exista pra outro usuário — risco real de vazamento de informação
Não é uma regra por DDD ou região — é determinística por usuário, e só o WhatsApp sabe a resposta. Por isso a única solução confiável é perguntar via checkPhones.
Workflow recomendado
Passo 1 — na importação de contatos (uma vez por contato):
curl -X POST "https://api.crialyconnect.com/functions/v1/api/{pid}/{sid}/checkPhones" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"numbers": ["5511987654321", "5521988887777"]}'Resposta:
{
"success": true,
"data": [
{"number": "5511987654321","exists": true,"jid": "551187654321@c.us"},
{"number": "5521988887777","exists": true,"jid": "5521988887777@c.us"},
{"number": "5511000000000","exists": false,"jid": null}
]
}Salve o jid no seu banco, associado ao contato. Note como o primeiro número entrou com 9 mas o JID veio sem (usuário legado), enquanto o segundo manteve o 9 (usuário moderno) — exatamente o tipo de diferença que precisa ser respeitada.
Passo 2 — em todas as mensagens, use o jid:
curl -X POST "https://api.crialyconnect.com/functions/v1/api/{pid}/{sid}/sendMessage" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"to": "551187654321@c.us", "type": "text", "message": "Olá!"}'Throughput & manutenção
checkPhonesaceita até 50 números por chamada — bom pra normalizar planilhas em batches.- Re-validar quando: sendMessage começar a falhar consistentemente pra um contato (provável troca de chip / re-registro do WhatsApp). Rode
checkPhonesde novo pra esse número e atualize o jid salvo.
FAQ
P: Posso simplesmente sempre tirar o 9 dos brasileiros?
Não. ~1% dos usuários ativos hoje tem o JID com o 9 (registros modernos). Tirar sempre vai falhar silenciosamente pra eles, e às vezes entregar a mensagem pra pessoa errada.
P: Vocês não podem auto-corrigir no servidor antes de enviar?
O WhatsApp não tem API determinística pra "normalizar" um número sem perguntar à rede. Auto-fallback (tentar com-9, depois sem-9) duplicaria a latência de toda mensagem e em casos raros entregaria pra pessoa errada antes de detectar erro. A solução elegante é o cliente normalizar uma vez na importação e usar o jid daí em diante.
P: O que é @c.us?
Sufixo de "contact" no protocolo do WhatsApp. Sempre presente em JIDs de pessoas físicas. Grupos usam @g.us. Você pode passar tanto o jid completo quanto só os dígitos no campo to do sendMessage — o servidor adiciona o sufixo automaticamente quando ausente. Mas guardar o jid completo no seu banco é mais claro pra debug.
P: Esse problema afeta só Brasil?
Sim, é específico da migração da ANATEL. Argentina tem um problema parecido com o "15", mas a regra muda por estado. Recomendação geral: em qualquer país onde o número tenha histórico de mudança regulatória, rode checkPhones na importação por garantia.
Endpoints — Grupos
Endpoints — Contatos & Conversas
Novos Endpoints — v1.0
Endpoints adicionados na versão 1.0. Inclui bloqueio de contatos, limpeza de conversas, labels e os endpoints não suportados pelo engine atual (retornam 501).
Implementados
Planejados (retornam 501)
Tipos de Mensagem Avançados
Todos via POST /{project_id}/{session_id}/sendMessage com o type correspondente.
😍 Reaction
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "reaction" | Sim | Tipo fixo |
| message | string | Sim | ID da mensagem alvo |
| text | string | Sim | Emoji para reagir ou "" para remover |
{
"to": "5541999999999",
"type": "reaction",
"message": "BAE5F2A4C988B231",
"text": "😍"
}📊 Poll
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "poll" | Sim | Tipo fixo |
| message | string | Sim | Pergunta da enquete |
| poll_options | string[] | Sim | Opções (mín. 2, máx. 12) |
| poll_multi_select | boolean | Não | Permite múltiplas respostas (padrão: false) |
{
"to": "5541999999999",
"type": "poll",
"message": "Qual o melhor horário?",
"poll_options": ["Manhã", "Tarde", "Noite"],
"poll_multi_select": false
}🗑️ Delete
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "delete" | Sim | Tipo fixo |
| message | string | Sim | ID da mensagem a apagar |
{
"to": "5541999999999",
"type": "delete",
"message": "BAE5F2A4C988B231"
}✏️ Edit
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "edit" | Sim | Tipo fixo |
| message | string | Sim | ID da mensagem a editar |
| text | string | Sim | Novo texto da mensagem |
{
"to": "5541999999999",
"type": "edit",
"message": "BAE5F2A4C988B231",
"text": "Texto corrigido"
}📍 Location
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "location" | Sim | Tipo fixo |
| message | object | Sim | Objeto com latitude, longitude, name, address |
| message.latitude | number | Sim | Latitude decimal (ex: -23.5505) |
| message.longitude | number | Sim | Longitude decimal (ex: -46.6333) |
| message.name | string | Não | Nome do local (exibido no WhatsApp) |
| message.address | string | Não | Endereço do local |
{
"to": "5541999999999",
"type": "location",
"message": {
"latitude": -23.5505,
"longitude": -46.6333,
"name": "Av. Paulista",
"address": "Av. Paulista, 1000 — São Paulo, SP"
}
}👤 Contact (vCard)
Envia o cartão de contato de um número existente no WhatsApp. O worker busca os dados de contato pelo JID automaticamente.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "contact" | "vcard" | Sim | Ambos são aceitos |
| message | string | Sim | Número do contato a compartilhar (JID ou número E.164) |
{
"to": "5541999999999",
"type": "contact",
"message": "5511988887777"
}↪️ Forward
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| to | string | Sim | Número do destinatário |
| type | "forward" | Sim | Tipo fixo |
| forward_from | string | Sim | ID serializado da mensagem original (message._serialized) |
| message | string | Não | Alias de forward_from — usado se forward_from não estiver presente |
{
"to": "5541999999999",
"type": "forward",
"forward_from": "true_5511999998888@c.us_3EB0A1B2C3D4E5F6"
}Use o campo message._serialized do webhook de mensagem recebida como valor de forward_from.
Webhooks
O Crialy Connect envia 3 tipos de webhook para o webhook_url configurado no projeto. O campo type na raiz identifica o tipo:
type: "message"— mensagem recebida (texto, mídia, reaction, poll, edit, revoke)type: "ack"— delivery receipt (sent, delivered, read)type: "status"— evento de sessão (connected, disconnected)
Os payloads incluem campos em camelCase (productId, phoneId) e seus aliases em snake_case (product_id, phone_id), além dos campos event e session_id para uso interno.
type: "message"
Disparado quando uma mensagem é recebida ou enviada (texto, mídia, reaction, poll, edit, revoke, fromMe, group events, calls, poll votes).
{
"type": "message",
"productId": "c96c07a0-7c5c-4470-9c92-b3eb2a81a36c",
"product_id": "c96c07a0-7c5c-4470-9c92-b3eb2a81a36c",
"phoneId": "4455ac83-6429-4b8e-bdbe-d269fd32c271",
"phone_id": "4455ac83-6429-4b8e-bdbe-d269fd32c271",
"receiver": "554191499739@c.us",
"conversation": "5511999998888@c.us",
"user": { "id": "5511999998888@c.us", "name": "João Silva", "phone": "5511999998888" },
"reply": "https://api.crialyconnect.com/functions/v1/api/{project_id}/{session_id}/sendMessage",
"timestamp": 1712764800,
"event": "message.received",
"session_id": "4455ac83-6429-4b8e-bdbe-d269fd32c271",
"message": {
"id": "3EB0A1B2C3D4E5F6",
"_serialized": "false_5511999998888@c.us_3EB0A1B2C3D4E5F6",
"type": "text",
"text": "Olá, quero agendar uma consulta",
"content": "Olá, quero agendar uma consulta",
"chatId": "5511999998888@c.us",
"fromMe": false,
"mediaUrl": null,
"from": "5511999998888@c.us",
"to": "554191499739@c.us",
"timestamp": 1712764800,
"metadata": { "message_id": "3EB0A1B2C3D4E5F6" }
}
}Campos do payload (v1.0)
| Campo | Tipo | Descrição |
|---|---|---|
| type | string | Sempre "message" |
| productId / product_id | string | UUID do projeto (camelCase + snake_case — ambos presentes) |
| phoneId / phone_id | string | UUID da sessão (camelCase + snake_case — ambos presentes) |
| receiver | string | null | JID do WhatsApp que recebeu a mensagem |
| conversation | string | JID do chat (individual ou grupo) |
| user.id | string | JID do remetente |
| user.name | string | Push name do WhatsApp |
| user.phone | string | Número sem sufixo JID |
| reply | string | URL de conveniência para responder via API |
| timestamp | number | Unix epoch (segundos) — timestamp da mensagem |
| event | string | Evento Crialy (ex: message.received) |
| session_id | string | UUID da sessão (igual a phoneId) |
| senderLid | string | null | LID bruto do remetente para rastreabilidade |
| message.id | string | ID curto da mensagem no WhatsApp |
| message._serialized | string | ID serializado completo no formato wwebjs <fromMe>_<chatId>_<messageId>. Sempre presente em mensagens reais — use como forward_from para forward. |
| message.type | string | text, image, audio, video, document, sticker, location, contact, reaction, poll, pollVoteUpdate, message_edit, info-message, call/* |
| message.text | string | null | Texto ou caption |
| message.content | string | null | Alias de text (compatibilidade) |
| message.chatId | string | JID do chat |
| message.fromMe | boolean | true se enviada pelo próprio número |
| message.from | string | JID do remetente |
| message.to | string | null | JID do destinatário |
| message.mediaUrl | string | null | URL pública da mídia no Supabase Storage |
| message.timestamp | number | Unix epoch (segundos) |
| message.metadata | object | Dados extras: mimetype, caption, poll_options, selected_options, call_type, etc. |
| message.metadata.lidUnresolved | boolean (opcional) | Quando true: remetente não pôde ser resolvido para phone canônico |
⚠️ fromMe loop prevention: Mensagens enviadas via API agora disparam webhook com message.fromMe: true. Seu sistema deve filtrar esse campo para evitar loops de resposta automática.
type: "ack"
Disparado quando uma mensagem enviada pelo próprio número é entregue ou lida pelo destinatário. ACKs são disparados apenas para mensagens com fromMe: true.
{
"type": "ack",
"productId": "c96c07a0-7c5c-4470-9c92-b3eb2a81a36c",
"phoneId": "4455ac83-6429-4b8e-bdbe-d269fd32c271",
"ack": {
"id": "3EB0A1B2C3D4E5F8",
"chatId": "5511999998888@c.us",
"ack": 2
},
"event": "message.delivered",
"session_id": "4455ac83-6429-4b8e-bdbe-d269fd32c271"
}Códigos do ack.ack
| Código | Significado |
|---|---|
| 1 | Sent — mensagem enviada ao servidor do WhatsApp (pode não ser disparado — confirmar via response do sendMessage) |
| 2 | Delivered — entregue ao dispositivo do destinatário (double check) |
| 3 | Read — lida pelo destinatário (blue check) |
Nota: ACKs são disparados apenas para mensagens enviadas pelo próprio número (fromMe: true). Mensagens recebidas de terceiros não geram eventos de ACK.
type: "status"
Disparado quando o estado da sessão muda (WhatsApp conectou ou desconectou). Anti-spam: só dispara no estado final — durante reconexões temporárias, nenhum webhook é enviado.
{
"type": "status",
"productId": "c96c07a0-7c5c-4470-9c92-b3eb2a81a36c",
"phoneId": "4455ac83-6429-4b8e-bdbe-d269fd32c271",
"content": {
"status": "connected",
"phone": "554191499739"
},
"event": "session.connected",
"session_id": "4455ac83-6429-4b8e-bdbe-d269fd32c271"
}Mapeamento event → type
| Evento Crialy | Webhook type |
|---|---|
| message.received | message |
| message.reaction | message |
| message.poll | message |
| message.poll_update | message |
| message.edit | message |
| message.revoke | message |
| message.received (pollVoteUpdate) | message — message.type: pollVoteUpdate |
| message.received (message_edit) | message — message.type: message_edit |
| message.received (info-message) | message — message.type: info-message (group_join/leave/update) |
| message.received (call/*) | message — message.type: call/ringing, call/accepted, call/timeout, call/rejected |
| message.sent | ack |
| message.delivered | ack |
| message.read | ack |
| session.connected | status |
| session.disconnected | status |
| session.auth_failure | status — content.status: auth_failure |
Política de Retry
Em caso de falha na entrega, o Crialy Connect realiza até 5 tentativas com backoff exponencial:
- 1ª tentativa: 1 segundo
- 2ª tentativa: 5 segundos
- 3ª tentativa: 30 segundos
- 4ª tentativa: 2 minutos
- 5ª tentativa: 10 minutos
Garantia de Identificação
O Crialy Connect v2 nunca expõe LID bruto em campos de identificação de usuário. Quando um LID não pode ser resolvido para o número de telefone canônico, o sistema sinaliza explicitamente:
metadata.lidUnresolved = trueno webhook de mensagem recebida quando o remetente não pode ser resolvidolid_unresolved = truepor participante em/getGroups/{group_id}quando o id do participante está em formato LID
Na grande maioria dos casos (>99%), todos os campos já vêm com o número canônico resolvido. O flag lidUnresolved é um edge case que afeta apenas primeiras mensagens de contatos totalmente novos.
Limitações Conhecidas
LID não resolvido no primeiro contato
Quando um contato totalmente novo envia a primeira mensagem, o webhook pode vir com metadata.lidUnresolved: true e campos de identificação em formato LID. Tipicamente na segunda mensagem o número já vem resolvido.
Participantes com LID em grupos novos
Em grupos recém-criados, alguns participantes desconhecidos do bot podem aparecer com lid_unresolved: true em /getGroups. Uma chamada subsequente depois de alguma interação pode retornar o participante já resolvido.
Ack batching do WhatsApp
Quando o destinatário abre o chat após múltiplas mensagens não lidas, todos os read acks chegam em batch numa janela de ~50-100ms. Isso é comportamento nativo do WhatsApp, não do Crialy Connect. Consumers devem esperar arrival clusters de read acks.
Códigos de Erro
| Código | Status | Descrição |
|---|---|---|
| 200 | OK | Requisição bem-sucedida |
| 201 | Created | Recurso criado com sucesso |
| 202 | Accepted | Mensagem aceita e enfileirada para envio. Será processada com rate limiting anti-ban. |
| 400 | Bad Request | Parâmetros inválidos ou ausentes |
| 401 | Unauthorized | API Key ausente ou inválida |
| 403 | Forbidden | Sem permissão para acessar o recurso |
| 404 | Not Found | Recurso não encontrado |
| 429 | Too Many Requests | Limite de requisições excedido |
| 500 | Internal Server Error | Erro interno do servidor |
| 501 | Not Supported | Endpoint não implementado pela engine atual (channels, catalog, labels management, stories). Planejado para versão futura. |
| 503 | Service Unavailable | Sessão em recuperação após falha do Chromium. Acompanha header Retry-After. |
Formato de Erro
{
"success": false,
"message": "Descrição do erro"
}Erro 503 — Sessão em Recuperação
O endpoint POST /sendMessage retorna 503 Service Unavailable quando a sessão está temporariamente indisponível por estar recuperando de uma falha do subsistema Chromium. Essa é a única exception do /sendMessage — todos os outros erros retornam 422.
Response headers:
HTTP/1.1 503 Service Unavailable
Retry-After: 15
Content-Type: application/jsonResponse body:
{
"error": "Session is recovering from Chromium subsystem failure",
"retry_after_seconds": 15
}Semântica: 422 = request inválida, não retente — corrija o input. 503 = servidor temporariamente indisponível, retente após Retry-After segundos. Consumidores que implementam retry exponencial honrando Retry-After continuam funcionando automaticamente.
Guia de Integração
Passo a passo para conectar um número WhatsApp via API e começar a enviar/receber mensagens.
1Criar sessão
POST /functions/v1/api/{project_id}/addPhone
Body: { "name": "WhatsApp do Cliente" }
→ Retorna session_id2Iniciar conexão
POST /functions/v1/api/{project_id}/{session_id}/connect
→ Worker começa a gerar QR Code3Obter QR Code (polling a cada 3s)
GET /functions/v1/api/{project_id}/{session_id}/qrCode
→ Retorna { "data": { "qr_code": "data:image/png;base64,..." } }4Renderizar no frontend
5Cliente escaneia o QR Code com WhatsApp
O usuário abre o WhatsApp → Configurações → Dispositivos conectados → Conectar dispositivo e escaneia o QR Code exibido.
6Verificar conexão (polling)
GET /functions/v1/api/{project_id}/{session_id}/status
→ Quando retornar "connected", parar polling e exibir sucesso⏱Timeout: Se após 60 segundos o QR não for escaneado, chame /connect novamente para gerar um novo QR Code.
Recebendo Mensagens
Configure o webhook do projeto para receber mensagens automaticamente:
POST /functions/v1/api/{project_id}/setWebhook
Body: { "webhook_url": "https://seu-site.com/webhook" }
// Toda mensagem recebida será enviada como POST para sua URL
// com o payload documentado na seção de Webhooks.