Lovable
Apps do Lovable têm duas partes: o front-end (React, roda no navegador de quem usa o app) e o back-end (Lovable Cloud / Supabase, com banco e Edge Functions). A chave da TopTalking só pode ficar no back-end.
Navegador (React) → Edge Function (Supabase) → API da TopTalking sem chave lê TOPTALKING_API_KEY https://platform.toptalking.com.br/v11. Gere a chave
Seção intitulada “1. Gere a chave”Em Configurações → Desenvolvedor, clique em Criar chave. Dê um nome como “App Lovable” e escolha:
- Somente leitura se o app só vai consultar (painéis, relatórios);
- Acesso total se vai enviar mensagens, criar contatos ou mover cards.
2. Guarde a chave como secret
Seção intitulada “2. Guarde a chave como secret”O nome do secret deve ser exatamente TOPTALKING_API_KEY.
- Jeito mais fácil: mande o primeiro prompt abaixo. Quando o Lovable precisar da chave, ele mostra um formulário seguro para adicionar o secret — cole a chave ali, nunca na conversa.
- Manual: adicione o secret nas configurações de secrets do backend do projeto (Lovable Cloud) ou, se o projeto usa uma conta Supabase própria, no painel da Supabase em Edge Functions → Secrets.
3. Prepare a ponte (Edge Function)
Seção intitulada “3. Prepare a ponte (Edge Function)”Cole este prompt no Lovable antes de pedir qualquer funcionalidade:
Vou integrar este app com a API da TopTalking (WhatsApp + CRM com Kanban).Leia a documentação completa antes de escrever código: https://platform.toptalking.com.br/llms-full.txt
Configure a base da integração:
1. Crie um secret chamado TOPTALKING_API_KEY (me peça para colar o valor pelo formulário seguro de secrets; não peça a chave no chat).2. Crie um módulo compartilhado para Edge Functions em supabase/functions/_shared/toptalking.ts com: - const API = "https://platform.toptalking.com.br/v1" - uma função toptalking(path, { method, body, idempotencyKey }) que faz fetch para `${API}${path}` com os cabeçalhos "Authorization: Bearer " + Deno.env.get("TOPTALKING_API_KEY"), "Content-Type: application/json" e, se informado, "Idempotency-Key". - Se a resposta não for 2xx, o corpo é { "error": { "type", "code", "message", "param", "request_id" } }: lance um erro com status, code, message e request_id. Em 429, inclua o valor do cabeçalho Retry-After.3. Crie a Edge Function "toptalking-status" que chama GET /me e devolve { organizacao: organization.name, acesso: api_key.access }. Ela deve exigir usuário logado (verificação de JWT do Supabase ligada) e tratar CORS (OPTIONS).4. Adicione na tela de configurações do app um botão "Testar conexão com a TopTalking" que chama essa função com supabase.functions.invoke e mostra o nome da organização ou o erro.
Regras obrigatórias para tudo o que fizermos com a TopTalking a partir de agora:- NUNCA chame https://platform.toptalking.com.br a partir do front-end (React) e NUNCA escreva a chave no código. A API bloqueia o navegador (sem CORS). Toda chamada sai de uma Edge Function usando o módulo _shared/toptalking.ts.- Não crie uma função "proxy" genérica que repassa qualquer caminho/método vindo do front-end: crie uma Edge Function por ação, validando os dados recebidos.- Envios de mensagem sempre com Idempotency-Key derivada do ID da operação.- Use só endpoints e campos que existem na documentação.4. Peça a funcionalidade
Seção intitulada “4. Peça a funcionalidade”Escolha um dos prompts prontos abaixo (ou adapte). Todos já repetem as regras de segurança — é proposital: o Lovable segue melhor quando a regra está no mesmo prompt.
Prompt: enviar WhatsApp quando alguém preencher o formulário
Seção intitulada “Prompt: enviar WhatsApp quando alguém preencher o formulário”Quero que, quando um visitante preencher o formulário de contato deste site, ele receba uma mensagem de boas-vindas no WhatsApp pela API da TopTalking e já entre no Kanban da empresa na etapa "Novo lead".
Documentação da API (leia antes de codar): https://platform.toptalking.com.br/llms-full.txt- URL base: https://platform.toptalking.com.br/v1- Autenticação: cabeçalho "Authorization: Bearer <chave>", com a chave lida de Deno.env.get("TOPTALKING_API_KEY") (secret do Supabase).
Segurança (obrigatório):- A chave NUNCA vai para o front-end nem para o código. O React chama só a nossa Edge Function; a Edge Function chama a TopTalking.- A API da TopTalking não aceita chamadas do navegador (sem CORS). Não tente chamá-la do React.
O que construir:
1. Formulário com: nome, WhatsApp com DDD e uma caixa obrigatória "Aceito receber mensagens no WhatsApp". Valide o telefone no front e no back: só dígitos; com DDD são 10 ou 11 dígitos; se não começar com 55, adicione 55.
2. Tabela "leads": id (uuid), nome, telefone, consentimento (boolean), status ('pendente' | 'enviado' | 'erro'), toptalking_contact_id, toptalking_message_id, erro_codigo, erro_mensagem, created_at. RLS: visitantes não leem nem escrevem direto; só a Edge Function (service role) grava. Usuários logados podem ler.
3. Edge Function "enviar-boas-vindas" (pública, sem exigir login, com CORS liberado só para o domínio do app): a. Recebe { nome, telefone, consentimento }. Recuse se consentimento não for true ou se o telefone for inválido. b. Anti-abuso: se já existe lead com o mesmo telefone nas últimas 24 h, não envie de novo (responda sucesso sem reenviar). c. Grave o lead com status 'pendente'. d. Busque as etapas: GET /v1/stages → { data: [{ id, name, is_final, ... }] }. Pegue o id da etapa com name "Novo lead" (se não existir, siga sem etapa). e. Crie/atualize o contato: POST /v1/contacts com { "phone": telefone, "name": nome, "stage_id": <id da etapa> }. A resposta (201 criado ou 200 já existia) traz o contato com "id". f. Envie a mensagem: POST /v1/messages com { "contact_id": <id do contato>, "text": "Olá, <nome>! 👋 Recebemos seu contato e logo vamos falar com você por aqui." } e o cabeçalho "Idempotency-Key: lead-<id do lead>-boas-vindas". A resposta 201 traz a mensagem com "id" e "status". g. Atualize o lead com status 'enviado', toptalking_contact_id e toptalking_message_id. h. Em erro, a TopTalking responde { "error": { "type", "code", "message", "request_id" } }: salve erro_codigo/erro_mensagem, status 'erro', e devolva ao front uma mensagem amigável. Exemplos de code: "channel_unavailable" (WhatsApp da empresa desconectado), "invalid_phone" (telefone inválido), "rate_limit_exceeded" (muitas requisições; respeite o cabeçalho Retry-After).
4. Depois de enviar o formulário, mostre "Pronto! Enviamos uma mensagem no seu WhatsApp." ou o erro amigável.
Não faça nenhum envio em massa: exatamente uma mensagem por formulário enviado.Prompt: painel com as conversas e as etapas do Kanban
Seção intitulada “Prompt: painel com as conversas e as etapas do Kanban”Quero uma página "Atendimento" (só para usuários logados) que mostre as conversas do WhatsApp da minha empresa organizadas pelas etapas do Kanban da TopTalking, com histórico de mensagens e a opção de mover um card de etapa.
Documentação da API (leia antes de codar): https://platform.toptalking.com.br/llms-full.txt- URL base: https://platform.toptalking.com.br/v1- Autenticação: cabeçalho "Authorization: Bearer <chave>", chave em Deno.env.get("TOPTALKING_API_KEY").
Segurança (obrigatório):- Nunca chame a TopTalking do front-end e nunca exponha a chave. Crie Edge Functions que exigem usuário logado (verificação de JWT do Supabase ligada) e chamam a TopTalking pelo servidor.- Não crie uma função genérica que repassa qualquer caminho: uma função por ação.
Edge Functions:1. "toptalking-quadro" (GET): - GET /v1/stages → lista de etapas em ordem: { data: [{ id, name, color, position, is_final }] }. - Para cada etapa: GET /v1/contacts?stage_id=<id>&archived=false&limit=100 → { data: [contatos], has_more, next_cursor }. Campos do contato: id, name, phone, stage { id, name, is_final }, ai_enabled, unread_count, last_message_at, last_message_preview, resolved_at. - Devolva [{ etapa, contatos }]. Guarde o resultado em cache por 60 segundos (limite da API: 120 requisições/minuto).2. "toptalking-conversa" (GET ?contact_id=<uuid>): - GET /v1/contacts/{contact_id}/messages?limit=50 → mensagens da mais recente para a mais antiga (campos: id, direction "inbound"|"outbound", type, text, media { url, mime_type, filename } ou null, status, created_at).3. "toptalking-mover" (POST { contact_id, stage_id }): - Confira que stage_id está entre os ids de GET /v1/stages (ou é null para tirar do Kanban). - PATCH /v1/contacts/{contact_id} com { "stage_id": stage_id } → devolve o contato atualizado.
Front-end:- Uma coluna por etapa (nome e cor da etapa). Cards com nome (ou telefone, se não houver nome), prévia da última mensagem, horário relativo em português ("há 5 min") e um selo com unread_count quando > 0.- Arrastar e soltar entre colunas (ou menu "Mover para…") chama toptalking-mover. Atualize de forma otimista e desfaça se der erro. Se a etapa de destino tiver is_final = true, peça confirmação: "Mover para esta etapa finaliza a conversa".- Clicar no card abre um painel lateral com o histórico (toptalking-conversa), em ordem cronológica: inbound à esquerda, outbound à direita, mostrando texto, imagem/arquivo (media.url) e status de entrega.- Botão "Atualizar" para recarregar o quadro.- Trate erros pelo error.code da resposta ({ "error": { "type", "code", "message", "request_id" } }).
Esta página não envia mensagens.Prompt: receber mensagens via webhook e salvar numa tabela
Seção intitulada “Prompt: receber mensagens via webhook e salvar numa tabela”Quero salvar no banco deste app as mensagens que os clientes mandam para o WhatsApp da empresa, recebendo os webhooks da TopTalking em uma Edge Function.
Documentação (leia as seções de Webhooks, Eventos e Verificar a assinatura): https://platform.toptalking.com.br/llms-full.txt
1. Tabela "mensagens_whatsapp": id (uuid, primary key = data.message.id), evento_id (uuid, unique = id do evento), contato_id (uuid), contato_nome (text), telefone (text), direcao (text), tipo (text), texto (text), midia_url (text), recebida_em (timestamptz = data.message.created_at), payload (jsonb com o evento inteiro), created_at (default now()). RLS: só usuários logados podem ler; ninguém escreve pelo front-end.
2. Edge Function pública "toptalking-webhook": - Desligue a verificação de JWT dela em supabase/config.toml ([functions.toptalking-webhook] verify_jwt = false), porque a TopTalking não envia token do Supabase. - Leia o corpo CRU com await req.text() ANTES de qualquer JSON.parse. - Valide o cabeçalho X-TopTalking-Signature, no formato "t=<unix em segundos>,v1=<hex>": v1 = HMAC-SHA256 em hexadecimal, com chave = Deno.env.get("TOPTALKING_WEBHOOK_SECRET") (o valor inteiro, começando com whsec_) e mensagem = t + "." + corpo cru. Use Web Crypto (crypto.subtle.importKey com HMAC/SHA-256 e crypto.subtle.verify, que compara em tempo constante). Rejeite com 401 se a assinatura não bater ou se a diferença entre agora e t for maior que 300 segundos. - Só então faça JSON.parse. O evento tem o formato { id, object: "event", type, created_at, organization_id, data }. - Se type = "message.received": grave em mensagens_whatsapp usando data.message (id, direction, type, text, media?.url, created_at) e data.contact (id, name, phone), com o cliente Supabase usando a service role (SUPABASE_SERVICE_ROLE_KEY) só dentro da função. Use upsert/on conflict do nothing: o mesmo evento pode chegar mais de uma vez. - Se type = "ping" (teste do painel) ou qualquer outro tipo: apenas responda 200. - Responda 200 rapidamente (em menos de 10 segundos). Qualquer resposta fora de 2xx faz a TopTalking tentar de novo.
3. Página "Mensagens recebidas" (só usuários logados) listando a tabela, mais recentes primeiro, com atualização em tempo real.
4. No fim, me diga: - a URL da função (https://<projeto>.supabase.co/functions/v1/toptalking-webhook) para eu cadastrar no painel da TopTalking em Configurações → Desenvolvedor → Webhooks → Novo webhook, assinando o evento message.received; - que preciso adicionar o secret TOPTALKING_WEBHOOK_SECRET com o "segredo de assinatura" (whsec_...) que o painel mostra.
Não use a chave de API da TopTalking no front-end e não escreva nenhum segredo no código.Prompt: lembrete automático no WhatsApp (mensagem agendada)
Seção intitulada “Prompt: lembrete automático no WhatsApp (mensagem agendada)”Quero que, ao criar um agendamento neste app, o cliente receba um lembrete no WhatsApp 24 horas antes, usando as mensagens agendadas da TopTalking (elas aparecem na Agenda do painel da empresa).
Documentação (leia antes de codar): https://platform.toptalking.com.br/llms-full.txt- URL base: https://platform.toptalking.com.br/v1- Autenticação: "Authorization: Bearer <chave>", chave em Deno.env.get("TOPTALKING_API_KEY"). Nunca no front-end nem no código.
1. Na tabela de agendamentos, adicione a coluna toptalking_lembrete_id (uuid, opcional).
2. Edge Function "agendar-lembrete" (exige usuário logado), recebe { agendamento_id }: - Leia nome, telefone (com DDD; adicione 55 se faltar) e data_hora do agendamento. - Calcule send_at = data_hora − 24 horas, em ISO 8601 COM fuso (ex.: "2026-12-01T09:00:00-03:00"). Se send_at já passou, não agende (send_at precisa estar no futuro e em até 365 dias). - POST /v1/scheduled-messages com { "to": telefone, "send_at": send_at, "title": "Lembrete de agendamento", "text": "Oi {{nome}}! Passando para lembrar do seu horário amanhã às HH:MM. Qualquer coisa, é só responder aqui." } e o cabeçalho "Idempotency-Key: agendamento-<id>-lembrete-24h". Atenção: {{nome}} é uma variável da própria TopTalking — mantenha as chaves duplas literalmente no texto. Troque só HH:MM. - A resposta 201 traz { id, status: "pending", ... }. Salve o id em toptalking_lembrete_id.
3. Edge Function "cancelar-lembrete", recebe { agendamento_id }: - DELETE /v1/scheduled-messages/{toptalking_lembrete_id}. Resposta 200 = cancelado (status "canceled"). - Se vier 409 com error.code "invalid_state", o lembrete já foi enviado ou não está mais pendente: só registre. - Limpe toptalking_lembrete_id.
4. Chame "agendar-lembrete" ao criar um agendamento; ao remarcar, chame "cancelar-lembrete" e depois "agendar-lembrete" (use uma Idempotency-Key nova na remarcação, por exemplo com a nova data: agendamento-<id>-lembrete-<data>). Ao cancelar o agendamento, chame "cancelar-lembrete".
Trate erros pelo error.code da resposta ({ "error": { "type", "code", "message", "request_id" } }).Prompt: tela para conectar um WhatsApp por QR Code
Seção intitulada “Prompt: tela para conectar um WhatsApp por QR Code”Quero uma página "Conectar WhatsApp" (só para usuários logados) onde eu crio uma conexão de WhatsApp na TopTalkinge leio o QR Code com o celular, sem precisar abrir o painel da TopTalking.
Documentação (leia antes de codar): https://platform.toptalking.com.br/llms-full.txtPasso a passo de referência: https://platform.toptalking.com.br/referencia/canais/- URL base: https://platform.toptalking.com.br/v1- Autenticação: "Authorization: Bearer <chave>", chave em Deno.env.get("TOPTALKING_API_KEY"). Nunca no front-end nem no código.
Edge Functions (exigem usuário logado; o front-end chama SÓ estas funções, nunca a TopTalking):1. "whatsapp-conectar" (POST { nome }): - POST /v1/channels com { "name": nome } e o cabeçalho "Idempotency-Key: <uuid gerado no clique>". - Resposta 201: { object: "channel_connection", channel: { id, name, phone, status }, status, qr_code, qr_code_generated_at, pairing_code }. - Se vier 422 com error.code "limit_reached", devolva a error.message (o plano não permite mais conexões). - Devolva { id: channel.id, status, qrCode: qr_code, telefone: channel.phone }.2. "whatsapp-status" (GET ?id=<channel_id>): - GET /v1/channels/{id}/qr → mesmo formato acima. Devolva { id, status, qrCode, telefone }. - Se status for "disconnected" e qr_code for null, chame POST /v1/channels/{id}/connect com {} para gerar um novo QR Code e devolva o resultado dele.
Front-end:- Botão "Conectar WhatsApp" → chama whatsapp-conectar.- Enquanto status for diferente de "connected", chame whatsapp-status a cada 4 segundos.- qrCode é uma imagem PNG em data URL ("data:image/png;base64,..."): use direto em <img src={qrCode}> (264×264). Se qrCode vier null, mostre "Gerando o QR Code…" (é normal nos primeiros segundos).- Instruções abaixo da imagem: "No celular: WhatsApp → Aparelhos conectados → Conectar um aparelho".- Quando status for "connected", pare de consultar e mostre "WhatsApp <telefone> conectado!".- Desista depois de 3 minutos sem leitura e mostre um botão "Gerar novo QR Code" (que chama whatsapp-status de novo).
Trate erros pelo error.code da resposta ({ "error": { "type", "code", "message", "request_id" } }).Receber webhooks numa Edge Function
Seção intitulada “Receber webhooks numa Edge Function”Resumo do que o prompt acima faz, para você conferir:
- A função
toptalking-webhookfica pública (verify_jwt = false), porque quem chama é a TopTalking, não um usuário logado. A segurança vem da assinatura. - Ela lê o corpo cru, valida
X-TopTalking-Signaturecom o secretTOPTALKING_WEBHOOK_SECRETe só então processa. O código de referência está em Verificar a assinatura (aba “Deno / Supabase Edge Function”). - No painel da TopTalking, em Configurações → Desenvolvedor → Webhooks → Novo webhook, cadastre
https://<seu-projeto>.supabase.co/functions/v1/toptalking-webhooke escolha os eventos. - Copie o segredo de assinatura (
whsec_...) e adicione como secretTOPTALKING_WEBHOOK_SECRETno Lovable. - Clique em Enviar teste e veja em Ver entregas se a resposta foi
200.
Problemas comuns
Seção intitulada “Problemas comuns”| Sintoma | Causa e solução |
|---|---|
| Erro de CORS no console do navegador | O app está chamando a TopTalking direto do React. Peça: “Mova a chamada para uma Edge Function; o front-end só chama a Edge Function”. |
401 invalid_api_key / missing_api_key |
O secret TOPTALKING_API_KEY não existe, tem outro nome ou foi colado com espaços. |
403 read_only_key |
A chave é Somente leitura. Crie outra com Acesso total para enviar ou alterar. |
422 channel_unavailable |
O WhatsApp da empresa está desconectado. Reconecte no painel em Configurações → Conexões. |
O webhook recebe 401 da própria Supabase |
A função está com verificação de JWT ligada. Desligue para toptalking-webhook. |
| Assinatura do webhook nunca bate | A função fez req.json() antes de validar. Valide com await req.text(). |
| A chave apareceu no código ou no chat | Revogue a chave no painel na hora e crie outra. |