Resposta curta: uma tela de conversa parecida com o WhatsApp Web, funcionando com a API oficial em coexistência, sai em três fases: interface, banco de dados e conexão com a API. É uma tela só, com a lista de conversas à esquerda e as mensagens à direita. O projeto de referência, construído por Israel Henrique, CTO da Datafy, usa Claude Design para o layout, VS Code com Claude Code ou Codex para escrever, Nuxt no front, Supabase com duas tabelas, Pusher para tempo real, a Datafy API para o WhatsApp e a Vercel para publicar, com o código liberado no GitHub. A ordem que faz diferença: coletar payloads reais antes de criar qualquer tabela.
Principais pontos
- Comece pela interface com dados falsos. A tela populada com informação inventada dá referência visual para o resto do projeto.
- Modele o banco a partir de payloads reais, copiados do log ao vivo do painel, e não do que você imagina que a Meta manda.
- Não dê à IA o controle do banco. Ela gera o SQL, você executa e confere.
- Consulta ao banco sai do servidor, com a chave de serviço, nunca do navegador.
- Webhook primeiro, tempo real depois. E, ao publicar, troque a URL do webhook do túnel para a de produção.
O que o projeto é, e o que não é
É uma tela única que recebe e envia mensagens pelo número conectado, em coexistência: a mesma linha continua funcionando no aplicativo do celular, e mensagem enviada do celular aparece na aplicação, com legenda quando é imagem.
Ele delimita desde o começo: não é um SaaS, não tem login, assinatura nem multiusuário. "O foco é entender o funcionamento da API, enviar e receber mensagens." Quem precisa de caixa de entrada para uma equipe liga a Datafy a um sistema de atendimento pronto.
As ferramentas
| Camada | Ferramenta | Papel |
|---|---|---|
| Design | Claude Design | Gera a interface a partir de um print do WhatsApp Web |
| Código | VS Code com Claude Code ou Codex | Escreve os componentes e o servidor |
| Front | Nuxt com Tailwind | Tela única, componentizada, modo claro e escuro |
| Banco | Supabase | Duas tabelas, e as políticas de acesso |
| Tempo real | Pusher | Entrega a mensagem na tela sem recarregar |
| Datafy API | API oficial, com webhook e token no painel | |
| Publicação | Vercel | Importa o repositório e sobe com as variáveis de ambiente |
A razão de usar uma parceira de tecnologia da Meta é a lista de etapas que desaparece: conta de desenvolvedor, aplicativo, infraestrutura de webhook, revisão, documentos e ainda se tornar Tech Provider para ter coexistência. Por que a API é um espelho da Cloud API.
Fase 1: a interface, com dados falsos
A tela tem duas áreas, e a divisão vira a árvore de componentes: à esquerda, conversas com cabeçalho, lista e item; à direita, mensagens com cabeçalho, lista, item e o campo de digitação flutuante. Os balões são por tipo, texto, áudio, imagem com ou sem legenda, documento, vídeo e figurinha, e os de mídia recebem um link público para renderizar. Um componente auxiliar compartilhado desenha só o horário e os tiques de status em todo balão. Modo claro e escuro entram nessa fase, porque acertar contraste depois custa mais.
Uma prática dessa fase vale para qualquer projeto com IA: criar um arquivo de documentação com o roteiro, com fases, estrutura de pastas e o que já foi feito. Ele mesmo diz que deveria ter feito isso no começo, e a utilidade apareceu no meio: quando a ferramenta de IA travou, a outra leu o mesmo arquivo e continuou de onde havia parado.
Fase 2: os payloads vêm antes das tabelas
É o que separa um banco que funciona de um que precisa de três migrations. Antes de criar tabela, mande para o número mensagens de tipos diferentes e copie o payload de cada uma no log ao vivo do painel. São cinco:
| Envio de teste | O que o payload ensina |
|---|---|
| Texto pelo celular do cliente | A estrutura básica, com o contato e o corpo da mensagem |
| Áudio | Que a mídia vem com um identificador, não com o arquivo |
| Imagem com legenda | Que a legenda existe e precisa de coluna |
| Mensagem enviada pelo app do WhatsApp Business | Que ela chega em outro evento, o eco do celular |
| Envio pela própria API | Que o retorno é só status, sem conteúdo |
O primeiro comando é o que devolve os seus identificadores, porque quem tem só o token não sabe o phone_number_id:
GET https://cloud.datafyapi.com.br/me
Authorization: Bearer sk_live_xxx
{
"cliente_id": "uuid-do-cliente",
"phone_number_id": "106540352242922",
"waba_id": "366634483210360",
"business_id": "123456789"
}
E o miolo do payload de uma mensagem recebida, que é o molde das duas tabelas:
{
"metadata": { "phone_number_id": "106540352242922" },
"contacts": [{
"profile": { "name": "Israel Henrique" },
"wa_id": "5541999999999",
"user_id": "ABCDEF1234567890"
}],
"messages": [{
"from": "5541999999999",
"id": "wamid.HBgNNTU0MTk5OTk5OTk5ORUCABIYFjNBM...",
"timestamp": "1759100000",
"type": "image",
"image": { "mime_type": "image/jpeg", "id": "1402849023948572", "caption": "nota fiscal" }
}]
}
Como abrir esse log e copiar payload por payload.
Três coisas que os payloads ensinam
O phone_number_id é do número conectado, não de quem mandou. Vem igual em toda mensagem. Quem trata esse campo como identificação do cliente erra a modelagem inteira, e é justamente o campo a guardar se um dia quiser mais de um número.
Quem mandou está em contacts, com nome, telefone e user_id, o identificador novo do WhatsApp nas APIs. A expectativa dele é que o telefone deixe de vir em algum momento, com a ressalva de que é previsão e não data. Guarde o user_id desde a primeira versão do banco. Sobre o user_id.
Envio pela API volta só como status, com o ID e sent, depois delivered, e read quando a pessoa tem a confirmação de leitura ativada. Se não tiver, o evento de leitura não chega.
As duas tabelas, e a primeira versão errada
Duas tabelas bastam. Conversas guarda o contato, o phone_number_id, o identificador da conta, o nome, o user_id e o texto da última mensagem. Mensagens guarda a conversa, o ID, a direção, o tipo, o corpo, a mídia e o horário. A primeira versão gerada pela IA tinha colunas que a API oficial não manda, e faltava uma que ela manda:
| Coluna proposta | Decisão |
|---|---|
| URL de avatar | Removida: a API oficial não envia foto de perfil |
| Contador de não lidas | Removida: não vem no payload |
| Tipo do arquivo e marcação de áudio de voz | Removidas nesse escopo |
| Legenda da mídia | Acrescentada: imagem e vídeo chegam com legenda |
Como a primeira migration já havia sido aplicada, a correção veio numa segunda: migration aplicada não se reescreve.
Não dê à IA o controle do banco
A IA sugeriu aplicar as mudanças pela linha de comando, controlando o banco direto. A recusa é explícita: "o banco de dados é o coração do projeto. Se dá o controle para ele, ele pode fazer muita coisa errada. No máximo você dá permissão para ela ler o teu banco de dados, mas nunca para mexer."
O combinado: a IA diz o que precisa e gera o SQL, e você cola no editor do banco e executa.
Segurança: chaves e políticas
As duas chaves do banco são diferentes. A pública pode aparecer no front. A de serviço nunca: fica só no servidor. Ele regenerou a de serviço depois de ela aparecer na gravação, que é o procedimento certo quando uma credencial é exposta.
O arquivo de exemplo de variáveis vai para o repositório público, e foi a IA que avisou que havia uma credencial real colada ali. Nele entram só os nomes das variáveis.
As políticas públicas saíram. A IA havia criado políticas que deixavam as tabelas legíveis por qualquer um com a chave pública. A correção foi removê-las e mover todas as consultas para uma pasta de servidor.
O token do número é rotacionável no painel. As variáveis do projeto são três: URL base da Datafy, token e phone_number_id.
Assinatura do webhook. Os webhooks da Datafy entregam os eventos com assinatura HMAC, nos cabeçalhos x-datafy-signature-256 e x-datafy-timestamp, mais o x-datafy-delivery-id, que serve de chave de idempotência. Ao validar, use o corpo cru da requisição: parsear e reserializar muda os bytes e a assinatura não fecha.
Desempenho: paginação, rodapé e cache
Conversas de 20 em 20, mensagens de 50 em 50, carregando mais conforme a rolagem. A lista abre na mensagem mais recente e rola para o fim também ao enviar, e mesmo com uma única mensagem ela fica colada embaixo. Detalhe da prática: mídia carrega de forma assíncrona e empurra a altura depois da rolagem, então a tela pode parar alguns pixels acima do fim.
Sem cache, voltar para uma conversa já aberta refaz a mesma consulta de 50 mensagens. Um armazenamento local por conversa resolve, e é o mesmo ponto de inserção que o tempo real usa para acrescentar a mensagem nova e mover a conversa para o topo.
Fase 3: webhook primeiro, tempo real depois
A ordem recomendada pela própria IA está certa: o webhook é a fundação e pode ser testado sozinho, sem conta externa.
- Crie o endpoint que recebe o evento, identifica ou cria a conversa e grava a mensagem.
- Suba um túnel para a porta da aplicação.
- Cadastre a URL no painel da Datafy, na aba de webhooks do número, marcando os dois eventos que importam: mensagens recebidas e mensagens enviadas pelo celular.
- Use o botão de teste do painel, que dispara um evento falso antes de existir tráfego real.
O primeiro teste dele deu 403, e a causa não era a Datafy: era o próprio framework, que bloqueia hosts não autorizados em desenvolvimento. Liberado o domínio do túnel e reiniciado o servidor, o evento voltou 200. A interface local do túnel, na porta 4040, mostra o que chegou mesmo quando a aplicação recusou.
Mídia: o identificador vira URL
O payload de mídia não traz arquivo, traz um identificador, e a URL que aparece ali não abre no navegador. O caminho é uma chamada com o ID:
GET https://cloud.datafyapi.com.br/media/1402849023948572
Authorization: Bearer sk_live_xxx
{
"url": "https://files.datafyapi.com.br/media/1402849023948572.jpg",
"mime_type": "image/jpeg",
"file_size": 184320
}
Essa URL é pública e vale 30 dias. O endpoint simplificado existe porque a mídia chega criptografada da Meta, e a descriptografia já foi resolvida na plataforma. No projeto, isso vira uma função chamada quando o webhook traz mídia, e o que vai para o banco é o link resolvido. Dois prazos não se confundem: o identificador do webhook expira em 7 dias na Meta, e a URL de download da Meta vale poucos minutos. O caminho completo da mídia.
Envio pelo campo de digitação
O campo de digitação chama um endpoint do seu servidor, que chama a Datafy:
POST https://cloud.datafyapi.com.br/v1/{phone_number_id}/messages
Authorization: Bearer sk_live_xxx
Content-Type: application/json
{
"messaging_product": "whatsapp",
"to": "5541999999999",
"type": "text",
"text": { "body": "Oi! Estou verificando o seu pedido agora." }
}
A resposta traz um ID e nada mais: o pedido foi aceito, não entregue. Na tela, o balão aparece na hora, de forma otimista, e o status e o eco chegam depois pelo webhook, sem duplicar, porque o ID é o mesmo. Para marcar como lida a mensagem do cliente há o endpoint simplificado POST /messages/read, que não pede o phone_number_id. Como a URL é a da Datafy e o corpo é o da documentação da Meta, qualquer modelo de IA já sabe montar essas chamadas.
Tempo real, e o que a tela precisa tratar
O caminho é de mão única: o webhook chega no servidor, que grava no banco e publica no serviço de tempo real, e a tela recebe. O front nunca fala direto com o banco, e o plano gratuito do Pusher atende até 100 conexões simultâneas.
Falta a parte que nenhum layout resolve, que são as regras da API oficial:
| Situação | O que a tela precisa fazer |
|---|---|
| Janela de 24 horas aberta | Campo de digitação livre, mensagem de serviço |
| Janela fechada | Bloquear o envio livre e exigir template |
| Requisição aceita com ID | Mostrar como enviando, nunca como entregue |
sent |
Um tique |
delivered |
Dois tiques |
read |
Dois tiques azuis, quando a confirmação existe |
failed |
Marcar erro na bolha, por exemplo serviço fora da janela ou falha no pagamento |
Quem abre e renova a janela é o cliente: resposta de atendente ou de automação não estende o prazo, e cada cliente tem a sua janela. Por isso a tabela de conversas guarda o horário da última mensagem recebida daquele contato, e a tela consulta esse horário antes de liberar o campo. Sem isso, a mensagem sai, volta HTTP 200 com ID, e a falha aparece depois no webhook de status, com o atendente achando que respondeu.
Publicar, e os limites do projeto
Código no repositório, projeto importado na plataforma de publicação e variáveis de ambiente coladas de uma vez. Depois do deploy vem o passo sem o qual nada funciona: trocar a URL do webhook no painel da Datafy, do túnel para a de produção. Enquanto o túnel está de pé, as mensagens continuam chegando por ele, o que dá a falsa impressão de que a publicação funcionou. Cuidado com a barra no fim da URL.
Dois limites ficam. É um número, por variável de ambiente: vários números mudam a modelagem, porque cada conversa passa a pertencer a um número. E a conversa apaga as mensagens em cascata, o que em mil mensagens apaga mil linhas de uma vez e pode travar.
Perguntas frequentes
Quantas tabelas o projeto precisa?
Duas: conversas e mensagens. As colunas certas saem dos payloads reais, não de suposição.
Por que coletar payloads antes de criar as tabelas?
Porque a API oficial manda menos do que a intuição espera, como a ausência de foto de perfil, e manda o que se esquece, como a legenda da mídia.
Posso deixar a IA aplicar as migrations?
A recomendação é não. A IA gera o SQL e você executa, porque no banco um erro dela não tem desfazer fácil.
Por que as consultas ao banco saem do servidor?
Porque as tabelas ficam sem acesso público, e só a chave de serviço lê. Ela nunca vai para o navegador.
O que fazer quando o webhook responde 403 no teste?
Olhe os hosts permitidos do seu framework em desenvolvimento, e a interface local do túnel na porta 4040 para ver o que chegou.
Como a tela sabe se pode enviar mensagem livre?
Guardando o horário da última mensagem recebida de cada cliente e comparando com as 24 horas. Fora da janela, só template, e a requisição pode ser aceita mesmo assim, falhando depois.
Isso serve como caixa de entrada para uma equipe?
Não como está: é um número, sem login e sem filas. Para atender com equipe, ligue a Datafy a um sistema de atendimento pronto.
Resumo para a sua integração
Siga a ordem: interface com dados falsos, coleta dos cinco payloads, duas tabelas modeladas em cima deles, webhook sem tempo real, mídia pelo identificador, envio pelo campo, tempo real e só então publicação. Guarde desde a primeira versão do banco o user_id, o phone_number_id, o ID da mensagem e o horário da última mensagem recebida: são os quatro campos que fazem diferença quando o projeto cresce.
Colete os cinco payloads antes de criar a primeira tabela
Conecte um número, chame GET /me para pegar o phone_number_id e mande para o número um texto, um áudio, uma imagem com legenda, uma mensagem pelo celular e um envio pela API. Copie os cinco payloads no log do painel e só então modele o banco.
Sem taxa de setup · Cancele quando quiser · Suporte por WhatsApp
Leia também
- Ver o payload das mensagens em tempo real
- Como receber mídia na API oficial do WhatsApp
- O telefone está sumindo do webhook
- A Datafy API é um espelho da Cloud API
Continue por aqui
- ImplementaçãoComo conectar um número na API oficial do WhatsApp em 5 minutos, sem burocracia
O passo a passo completo da conexão: criar o número no painel, escolher o portfólio empresarial, chip novo ou aplicativo existente, escanear o QR code e cadastrar a forma de pagamento na BM.
Ler mais - ImplementaçãoComo criar um template de WhatsApp passo a passo, no painel da Meta
Gerenciar modelos, categoria, cabeçalho com imagem, corpo com variáveis e exemplo obrigatório, botões e envio para análise. E o que a categoria faz no seu custo.
Ler mais - ImplementaçãoComo enviar imagem, documento e áudio pela API oficial do WhatsApp
Por link ou por identificador, no mesmo endpoint de mensagens. Legenda na imagem, nome no documento, e o que faz o áudio virar bolha de voz em vez de anexo.
Ler mais