Crialy Connect API

    Crialy Connect API

    v1.0.1

    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.

    Base URL
    text
    https://api.crialyconnect.com/functions/v1/api
    Autenticação

    Todas 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

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

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

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

    bash
    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

    • checkPhones aceita 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 checkPhones de 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

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"reaction"SimTipo fixo
    messagestringSimID da mensagem alvo
    textstringSimEmoji para reagir ou "" para remover
    json
    {
      "to": "5541999999999",
      "type": "reaction",
      "message": "BAE5F2A4C988B231",
      "text": "😍"
    }

    📊 Poll

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"poll"SimTipo fixo
    messagestringSimPergunta da enquete
    poll_optionsstring[]SimOpções (mín. 2, máx. 12)
    poll_multi_selectbooleanNãoPermite múltiplas respostas (padrão: false)
    json
    {
      "to": "5541999999999",
      "type": "poll",
      "message": "Qual o melhor horário?",
      "poll_options": ["Manhã", "Tarde", "Noite"],
      "poll_multi_select": false
    }

    🗑️ Delete

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"delete"SimTipo fixo
    messagestringSimID da mensagem a apagar
    json
    {
      "to": "5541999999999",
      "type": "delete",
      "message": "BAE5F2A4C988B231"
    }

    ✏️ Edit

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"edit"SimTipo fixo
    messagestringSimID da mensagem a editar
    textstringSimNovo texto da mensagem
    json
    {
      "to": "5541999999999",
      "type": "edit",
      "message": "BAE5F2A4C988B231",
      "text": "Texto corrigido"
    }

    📍 Location

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"location"SimTipo fixo
    messageobjectSimObjeto com latitude, longitude, name, address
    message.latitudenumberSimLatitude decimal (ex: -23.5505)
    message.longitudenumberSimLongitude decimal (ex: -46.6333)
    message.namestringNãoNome do local (exibido no WhatsApp)
    message.addressstringNãoEndereço do local
    json
    {
      "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.

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"contact" | "vcard"SimAmbos são aceitos
    messagestringSimNúmero do contato a compartilhar (JID ou número E.164)
    json
    {
      "to": "5541999999999",
      "type": "contact",
      "message": "5511988887777"
    }

    ↪️ Forward

    CampoTipoObrigatórioDescrição
    tostringSimNúmero do destinatário
    type"forward"SimTipo fixo
    forward_fromstringSimID serializado da mensagem original (message._serialized)
    messagestringNãoAlias de forward_from — usado se forward_from não estiver presente
    json
    {
      "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.

    message

    type: "message"

    Disparado quando uma mensagem é recebida ou enviada (texto, mídia, reaction, poll, edit, revoke, fromMe, group events, calls, poll votes).

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

    CampoTipoDescrição
    typestringSempre "message"
    productId / product_idstringUUID do projeto (camelCase + snake_case — ambos presentes)
    phoneId / phone_idstringUUID da sessão (camelCase + snake_case — ambos presentes)
    receiverstring | nullJID do WhatsApp que recebeu a mensagem
    conversationstringJID do chat (individual ou grupo)
    user.idstringJID do remetente
    user.namestringPush name do WhatsApp
    user.phonestringNúmero sem sufixo JID
    replystringURL de conveniência para responder via API
    timestampnumberUnix epoch (segundos) — timestamp da mensagem
    eventstringEvento Crialy (ex: message.received)
    session_idstringUUID da sessão (igual a phoneId)
    senderLidstring | nullLID bruto do remetente para rastreabilidade
    message.idstringID curto da mensagem no WhatsApp
    message._serializedstringID serializado completo no formato wwebjs <fromMe>_<chatId>_<messageId>. Sempre presente em mensagens reais — use como forward_from para forward.
    message.typestringtext, image, audio, video, document, sticker, location, contact, reaction, poll, pollVoteUpdate, message_edit, info-message, call/*
    message.textstring | nullTexto ou caption
    message.contentstring | nullAlias de text (compatibilidade)
    message.chatIdstringJID do chat
    message.fromMebooleantrue se enviada pelo próprio número
    message.fromstringJID do remetente
    message.tostring | nullJID do destinatário
    message.mediaUrlstring | nullURL pública da mídia no Supabase Storage
    message.timestampnumberUnix epoch (segundos)
    message.metadataobjectDados extras: mimetype, caption, poll_options, selected_options, call_type, etc.
    message.metadata.lidUnresolvedboolean (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.

    ack

    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.

    json
    {
      "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ódigoSignificado
    1Sent — mensagem enviada ao servidor do WhatsApp (pode não ser disparado — confirmar via response do sendMessage)
    2Delivered — entregue ao dispositivo do destinatário (double check)
    3Read — 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.

    status

    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.

    json
    {
      "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 CrialyWebhook type
    message.receivedmessage
    message.reactionmessage
    message.pollmessage
    message.poll_updatemessage
    message.editmessage
    message.revokemessage
    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.sentack
    message.deliveredack
    message.readack
    session.connectedstatus
    session.disconnectedstatus
    session.auth_failurestatus — 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 = true no webhook de mensagem recebida quando o remetente não pode ser resolvido
    • lid_unresolved = true por 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ódigoStatusDescrição
    200OKRequisição bem-sucedida
    201CreatedRecurso criado com sucesso
    202AcceptedMensagem aceita e enfileirada para envio. Será processada com rate limiting anti-ban.
    400Bad RequestParâmetros inválidos ou ausentes
    401UnauthorizedAPI Key ausente ou inválida
    403ForbiddenSem permissão para acessar o recurso
    404Not FoundRecurso não encontrado
    429Too Many RequestsLimite de requisições excedido
    500Internal Server ErrorErro interno do servidor
    501Not SupportedEndpoint não implementado pela engine atual (channels, catalog, labels management, stories). Planejado para versão futura.
    503Service UnavailableSessão em recuperação após falha do Chromium. Acompanha header Retry-After.

    Formato de Erro

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

    bash
    HTTP/1.1 503 Service Unavailable
    Retry-After: 15
    Content-Type: application/json

    Response body:

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

    bash
    POST /functions/v1/api/{project_id}/addPhone
    Body: { "name": "WhatsApp do Cliente" }
    → Retorna session_id

    2Iniciar conexão

    bash
    POST /functions/v1/api/{project_id}/{session_id}/connect
    → Worker começa a gerar QR Code

    3Obter QR Code (polling a cada 3s)

    bash
    GET /functions/v1/api/{project_id}/{session_id}/qrCode
    → Retorna { "data": { "qr_code": "data:image/png;base64,..." } }

    4Renderizar no frontend

    html
    
    

    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)

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

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