API da Extensão
Endpoints que a extensão do Chrome consome e que o painel administrativo expõe:
licença, perfil e agente de IA por instalação, além de conta, checkout e gestão administrativa.
Todas as respostas são application/json (exceto redirects).
https://www.geracaoderesultado.com.br/extensaoAutenticação
As rotas /admin/* exigem a senha de administrador, enviada no cabeçalho
x-admin-pass (ou no corpo, campo adminPass). As rotas da extensão
(/extensao/api/*) e de usuário são públicas — a vinculação é feita pelo
installId e/ou pelo número de WhatsApp conectado.
# Exemplo de chamada autenticada (admin) curl -H "x-admin-pass: SUA_SENHA" \ https://www.geracaoderesultado.com.br/admin/licencas
Instalar e ativar a API
A API não é um servidor que você aponta e usa: ela conversa com o WhatsApp Web aberto no computador do atendente, através da extensão. Por isso a ativação tem três peças, e as três precisam estar de pé.
no Chrome do atendentegerado na própria extensãohttps://bridge.geracaoderesultado.com.brPela Chrome Web Store, ou sem compactação (chrome://extensions →
"Carregar sem compactação" → escolha a pasta do projeto). Depois, entre com o e-mail e a
senha da assinatura: é isso que amarra a instalação à conta.
No WhatsApp Web, com a extensão ativa: trilha lateral → API → botão Gerar token → Copiar. Ligue também Ativar / Desativar API.
| Campo | Como se comporta |
|---|---|
| Token de acesso | Somente leitura. Quem emite é o servidor — não se digita nem se cola. |
| URL do bridge / Bridge HTTP | Chegam prontos do painel. Não são editáveis: são infraestrutura do provedor. |
| Testar conexão | Verde = servidor respondeu e aceitou o token. Vermelho = recusou. |
A chamada mais segura para testar: ela não envia nada a ninguém, só lê as etiquetas.
curl -s "https://bridge.geracaoderesultado.com.br/api/listar-etiquetas/SEU_TOKEN"
| Resposta | Significa |
|---|---|
{"ok":true,"etiquetas":[…]} | Tudo funcionando, ponta a ponta. |
nenhuma_extensao_conectada | Token válido — falta o WhatsApp Web aberto, ou a API está desligada na extensão. |
401 token_invalido | Token errado, já rotacionado, ou licença vencida. |
nenhuma_extensao_conectada é boa notícia em um primeiro
teste: só aparece depois que o token foi reconhecido.Para o seu sistema ser avisado das mensagens: trilha → WebHook → informe a URL, marque os eventos e todos os campos em "Dados a enviar".
evento e timestamp são
garantidos — o resto só chega se estiver marcado. Detalhes do formato na seção
Webhooks.| Sintoma | Onde olhar |
|---|---|
Tudo dá token_invalido | /diagnostico na bridge — diz se o segredo dela é aceito pela plataforma |
| Envio some sem erro | Console do WhatsApp Web (F12, nível Verbose): as linhas começam com [CRM] |
| Webhook não chega | Extensão → WebHook → últimos 30 envios, com status HTTP e o corpo enviado |
| Mensagem enviada por você não chega no webhook | É esperado: ela dispara mensagem_enviada, não mensagem_recebida |
WhatsApp — enviar mensagens
Estas rotas ficam no bridge, não no site: é ele que leva o comando até o
WhatsApp Web onde a extensão está conectada. O token vai no caminho e identifica
o assinante — é ele que decide por qual WhatsApp a mensagem sai.
https://bridge.geracaoderesultado.com.br/api/<ação>/<TOKEN>O token é gerado na própria extensão (trilha → API → botão ↻). Ninguém digita nem cola: quem emite é o servidor. Gerar um novo invalida o anterior. Não compartilhe — quem tiver o token manda mensagem pelo seu número.
Envia uma mensagem de texto. Também aceita GET com os mesmos campos na query.
| Nome | Em | Descrição |
|---|---|---|
| token * | path | Token do assinante, gerado na extensão. |
| numero * | body | Qualquer formatação serve — só os dígitos são usados. Aceita também telefone, phone, number, to. |
| texto * | body | Conteúdo da mensagem. Aceita também mensagem, message, text, body. |
curl -X POST "https://bridge.geracaoderesultado.com.br/api/enviar-texto/SEU_TOKEN" \ -H "content-type: application/json" \ -d '{"numero":"5511988880001","texto":"Olá!"}'
{ "ok": true }Envia mídia. Prefira url: o bridge baixa o arquivo no servidor e entrega
à extensão. Assim você não precisa subir o arquivo na chamada — base64 infla 33% e trava
payloads grandes. O teto do download é 64 MB; acima disso a resposta é
arquivo_muito_grande.
| Nome | Em | Descrição |
|---|---|---|
| numero * | body | Destinatário. |
| url * | body | Endereço público do arquivo. Alternativa: base64. |
| legenda | body | Texto junto da mídia (imagem, vídeo e documento). |
| nomeArquivo | body | Nome exibido no documento. |
curl -X POST ".../api/enviar-documento/SEU_TOKEN" \ -H "content-type: application/json" \ -d '{"numero":"5511988880001", "url":"https://cdn.seu-site.com/boleto.pdf", "nomeArquivo":"boleto.pdf"}'
{ "ok": true }WhatsApp — etiquetas e notas
Lista as etiquetas do WhatsApp Business da conta conectada. Não envia nada — é a chamada mais segura para conferir se a integração está de pé.
{ "ok": true, "etiquetas": [
{ "id": "1", "nome": "Fazer Matrícula", "cor": "#64C4FF" }
]}
Aplica ou remove uma etiqueta em um ou vários números.
{
"numeros": ["5511988880001", "5511988880002"],
"etiquetaId": "1",
"acao": "adicionar"
}{ "ok": true, "resultados": [
{ "numero": "5511988880001", "ok": true }
]}Cria uma anotação interna no contato. A nota é da extensão — o contato não vê nada.
{ "numero": "5511988880001", "texto": "Ligar amanhã" }
Apaga uma mensagem. Use o dados.id que veio no webhook (sinônimos id,
messageId).
| Nome | Descrição |
|---|---|
| mensagemId * | Id da mensagem a apagar. |
| paraTodos | true apaga para o contato também (revoke); false (padrão) só do seu lado. |
{ "mensagemId": "true_5511999999999@c.us_ABC123", "paraTodos": true }
paraTodos:false.
Erros: revoke_so_para_mensagem_propria, fora_da_janela,
mensagem_nao_encontrada.WhatsApp — grupos e comunidades
Para o WhatsApp, uma comunidade é um grupo que tem subgrupos — por isso as
duas coisas usam as mesmas rotas. Na listagem, ehComunidade distingue.
O identificador de um grupo termina em @g.us. Ele também serve como
destino de envio: mande em chatId (ou grupo) nas rotas de envio, em
vez de numero.
Lista grupos e comunidades da conta conectada.
{ "ok": true, "grupos": [
{ "chatId": "1203630000000001@g.us",
"nome": "Turma 2026",
"participantes": 42,
"ehComunidade": false,
"subgrupos": 0 }
]}
Participantes do grupo, indicando quem é administrador.
{ "ok": true, "membros": [
{ "numero": "5511988880001", "nome": "Ana", "admin": true }
]}
Cria um grupo já com os participantes.
{ "nome": "Turma 2026",
"numeros": ["5511988880001", "5511988880002"] }{ "ok": true,
"chatId": "1203630000000001@g.us" }Uma rota para as quatro operações. acao aceita
adicionar, remover, promover e rebaixar.
{ "chatId": "1203630000000001@g.us",
"numeros": ["5511988880003"],
"acao": "promover" }
Os subgrupos vinculados a uma comunidade.
{ "ok": true, "subgrupos": ["1203630000000002@g.us"] }
Qualquer rota de envio aceita grupo: troque numero por chatId.
{ "chatId": "1203630000000001@g.us",
"texto": "Aviso para a turma" }
WhatsApp — erros e limites
| Resposta | O que houve |
|---|---|
401 token_invalido | Token desconhecido, revogado, ou licença vencida. |
404 rota_desconhecida | Ação que não existe. |
405 use_post | Essa ação só aceita POST. |
nenhuma_extensao_conectada | O WhatsApp Web daquele assinante não está aberto, ou a API está desligada na extensão. |
numero_nao_existe_no_whatsapp | O número não tem conta no WhatsApp. |
numero_obrigatorio | Faltou o campo. A resposta traz camposRecebidos e camposAceitos para você ver o que chegou. |
| Tipo | Limite | Observação |
|---|---|---|
| Imagem, vídeo, áudio | ~16 MB | Imposto pelo WhatsApp, não pela API. |
| Documento | ~2 GB | Use enviar-documento para arquivos grandes. |
Envio por base64 | ~45 MB | O corpo HTTP é cortado em 60 MB, e base64 infla ~33%. Prefira url. |
Webhooks — a extensão avisa o seu sistema
Caminho inverso da API de envio: aqui é a extensão que chama você. Para cada
evento marcado, ela faz um POST na URL configurada em trilha → WebHook, com
Content-Type: application/json. Não há resposta esperada além de um 2xx.
{
"evento": "mensagem_recebida",
"timestamp": "2026-07-22T14:40:00.000Z",
"numero": "5511958254568",
"nome": "Vitória Eduarda",
"foto": "https://...",
"etiqueta": "Estágios",
"perfil": { "isBusiness": false },
"dados": { "id": "ABCD", "texto": "Olá", "tipo": "chat",
"ts": 1784750700, "direcao": "recebida" },
"usuario": { "numero": "551133387242" }
}
numero é telefone ou null — nunca LID.
O WhatsApp novo identifica contatos por <digitos>@lid, que não é telefone
(tem 15 a 17 dígitos). A extensão resolve antes de disparar, e o LID original vai em
lid, à parte. Se não conseguir resolver, numero vem
null — nunca o LID. Assim é impossível cadastrar um LID como telefone por
engano; trate numero: null como "contato sem telefone conhecido" e use o
lid só para agrupar mensagens da mesma conversa.evento e timestamp são garantidos.
Todos os outros campos são opcionais por configuração: aparecem apenas se o assinante
marcou o campo correspondente em "Dados a enviar" daquele webhook. Se ele não marcar nada,
chega só { evento, timestamp }. Trate todo campo como ausente até prova em
contrário — não assuma nem o numero.| Marcação | Campo no corpo |
|---|---|
| Dados do evento | dados — varia por evento (ver abaixo) |
| Número | numero |
| Nome | nome |
| Foto | foto |
| Etiqueta | etiqueta |
| Perfil do contato | perfil |
| Usuário logado | usuario — o número que está conectado |
Webhooks — eventos e o que vem em dados
| Evento | Quando dispara | dados |
|---|---|---|
mensagem_recebida | chegou mensagem | { id, texto, tipo, ts, direcao } |
mensagem_enviada | saiu mensagem | idem, direcao: "enviada" |
etiqueta | etiqueta aplicada ao contato | { etiquetaId, abaId } |
crm | lead muda de etapa no funil | etapa de origem e destino |
respostas_rapidas | resposta rápida enviada | { atalho, titulo } |
encerrar_atendimento | atendimento encerrado | — |
agendamento | mensagem agendada disparou | { texto, id, recorrencia } |
auto_atendimento | regra ou IA respondeu | { regraId, via } ou { via: "ia", agente } |
follow_up | passo de follow-up executado | { sequencia, passo } |
mensagem_apagada | mensagem apagada por qualquer lado, inclusive direto no celular | { id, direcao, paraTodos, ts } |
mensagem_apagada traz em dados.id o mesmo id da
mensagem original que você recebeu antes — é assim que você casa o apagamento com o registro que
já tinha. direcao diz se a mensagem apagada era sua ou do contato.anuncio_instagram aparece na lista de
eventos da extensão mas nunca é emitido hoje — não construa nada em cima dele. E
status (status do atendimento) é emitido internamente, mas não pode ser
assinado, então nunca chega a um webhook.Em mensagem_recebida com mídia, dados.midia traz só os
metadados — { mimetype, tamanho, duracao, nomeArquivo, isVoz }. Os bytes não vão
no webhook (um áudio de 10 MB em base64 no corpo de cada evento inviabilizaria o recebimento).
Peça o arquivo depois, com o dados.id da mensagem:
dados.texto é a legenda (vazia quando não há) — a
miniatura que o WhatsApp guarda junto não é enviada. Se você recebeu um paredão começando
com /9j/4AAQ… no lugar do texto, é versão antiga da extensão: recarregue-a.Também aceita POST com o mesmo campo no corpo.
| Nome | Descrição |
|---|---|
| mensagemId * | O dados.id que veio no webhook. Sinônimos: id, messageId. |
| limiteMB | Teto para este pedido. O padrão e o máximo são 64 MB — pedir mais não aumenta. |
GET /api/baixar-midia/SEU_TOKEN
?mensagemId=ABCD123{ "ok": true,
"base64": "data:audio/ogg;base64,T2dnUw...",
"mimetype": "audio/ogg; codecs=opus",
"tamanho": 48213 }mensagem_nao_encontrada | O id não existe no WhatsApp conectado (mensagem apagada, outro número) ou está fora do formato de id de mensagem. |
sem_midia_nesta_mensagem | A mensagem existe, mas é texto — não há arquivo para baixar. Vem com tipo. |
muito_grande | Passa do teto; vem com tamanho e limiteMB. |
nenhuma_extensao_conectada | WhatsApp Web fechado — o arquivo mora lá, não no servidor. |
dados.id e peça só quando for usar.Webhooks — entrega e segurança
| Cabeçalho | Para quê |
|---|---|
X-GR-Evento | Nome do evento — o mesmo que vem em evento no corpo. |
X-GR-Entrega | Identificador da entrega. É o mesmo em todas as tentativas — use-o para não gravar o evento duas vezes. |
X-GR-Tentativa | 1 na primeira, 2… nas retentativas. |
X-GR-Timestamp | Unix em segundos de quando foi assinado. Só com chave configurada. |
X-GR-Signature | sha256=<hex>. Só com chave configurada. |
Em trilha → WebHook há um campo Chave secreta (com botão
Gerar). Preenchido, todo POST leva X-GR-Signature. Guarde a mesma chave do seu
lado e recuse o que não bater — é o que impede alguém que descobriu a sua URL de inventar leads e
mensagens no seu sistema.
A assinatura é o HMAC-SHA256 de <timestamp>.<corpo cru>.
O timestamp entra na conta de propósito: assinar só o corpo deixaria alguém capturar uma entrega
válida e reenviá-la para sempre.
// Node/Express — o corpo tem de ser o RAW, antes de virar JSON app.post("/webhook/gr", express.raw({ type: "application/json" }), (req, res) => { const ts = req.get("X-GR-Timestamp"); const sig = req.get("X-GR-Signature"); const esperada = "sha256=" + crypto.createHmac("sha256", SEGREDO) .update(ts + "." + req.body).digest("hex"); // timingSafeEqual, não ==: comparar string a string vaza o segredo aos poucos if (!sig || sig.length !== esperada.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(esperada))) return res.sendStatus(401); // recusa o que for velho: sem isso a assinatura continua válida para sempre if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return res.sendStatus(401); // dedupe: a MESMA entrega pode chegar de novo se a sua resposta se perdeu if (jaProcessei(req.get("X-GR-Entrega"))) return res.sendStatus(200); res.sendStatus(200); // responda ANTES de processar processarEmFila(JSON.parse(req.body)); });
# PHP $corpo = file_get_contents('php://input'); $ts = $_SERVER['HTTP_X_GR_TIMESTAMP']; $esperada = 'sha256=' . hash_hmac('sha256', $ts . '.' . $corpo, $SEGREDO); if (!hash_equals($esperada, $_SERVER['HTTP_X_GR_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
https://seu-sistema.com/webhook/gr/<aleatorio>) e recuse o resto. Preencher a
chave é melhor.Falhou, a extensão tenta de novo: 1, 2, 10 e 30 minutos depois (cinco tentativas no total, contando a primeira). A fila é conferida uma vez por minuto e fica gravada em disco, então ela sobrevive ao navegador ser fechado e reaberto.
| O que o seu endpoint respondeu | O que acontece |
|---|---|
2xx | Entregue. Fim. |
5xx, 429, 408, ou nem respondeu | Retenta na escala acima. |
Outro 4xx | Não retenta. É o seu sistema dizendo "não" — URL errada, token vencido, corpo recusado. Repetir daria a mesma resposta. |
X-GR-Entrega,
que é idêntico em todas as tentativas.Ainda assim, responda 2xx rapidamente e processe em fila do seu lado: a retentativa é rede de segurança, não substituto de um recebimento rápido.
Os últimos 30 envios ficam registrados na extensão (trilha → WebHook), com status HTTP, erro e o corpo enviado — incluindo quando a próxima tentativa vai sair e quando desistimos. É por ali que se depura uma integração que parou de receber.
Pegue uma URL descartável em webhook.site, cole na extensão, marque
mensagem_recebida e todos os campos, ative e mande uma mensagem para o número
conectado. O POST chega na hora — e com todos os campos marcados você vê o payload máximo, que é
o que o seu recebimento deve suportar.
API da Extensão
A extensão consulta estes endpoints ao instalar e a cada 30 minutos (sincronização).
Retorna o status da licença, o perfil do usuário e a configuração do agente de IA. Vincula a instalação a um usuário pelo installId ou pelo whatsapp informado.
| Nome | Em | Descrição |
|---|---|---|
| installId * | path | Identificador único gerado na instalação da extensão. |
| query | Número conectado no WhatsApp Web (só dígitos). Usado para vincular a conta. |
# GET /extensao/api/config/abc123?whatsapp=5554999480043
{
"success": true,
"status": "ativa",
"perfil": {
"nome": "Lauro",
"email": "lauro@ger.com",
"segmento": "educacao"
},
"licenca": {
"ativa": true,
"plano": "Premium",
"validade": "2026-08-12T22:46:50.465Z",
"diasRestantes": 24
},
"ia": { "ativa": false },
"tutoriaisUrl": "…/extensao/tutoriais",
"painelUrl": "…/extensao",
"docUrl": "…/extensao/api"
}Registra a instalação e devolve a URL para onde a extensão deve levar o usuário logo após instalar (landing/download).
{ "success": true, "url": "https://www.geracaoderesultado.com.br/extensao/" }
Chamado quando a extensão é removida. Registra a desinstalação e faz redirect para a página de desinstalação configurada (ou para /).
# 302 Redirect
Conta & Licenças
Cadastra um usuário usando um código de licença válido e ainda não utilizado. A licença define o plano e a validade.
{
"nome": "Lauro",
"email": "lauro@ger.com",
"whatsapp": "5554999480043",
"senha": "••••••",
"codigoLicenca": "A1B2C3D4"
}{ "ok": true }
// 400
{ "ok": false,
"error": "licença inválida ou já usada" }Autentica o usuário e devolve os dados da conta, os dias restantes de licença e a URL de download da extensão. O Dashboard grava a resposta em localStorage.wa_user.
{
"email": "lauro@ger.com",
"senha": "••••••"
}{
"ok": true,
"usuario": {
"nome": "Lauro",
"email": "lauro@ger.com",
"whatsapp": "5554999480043",
"plano": "Premium",
"diasRestantes": 24,
"ativaAte": "2026-08-12T…"
},
"downloadUrl": "…/extensao/wa-eadcom.zip"
}Cria uma sessão de pagamento no Stripe (modo teste) para o total do plano montado no checkout e devolve a URL de pagamento. Sem chave Stripe configurada, retorna stripe_nao_configurado e o front cai no fallback do WhatsApp.
{
"total": 1079,
"descricao": "Base + Extensão",
"email": "lauro@ger.com",
"recorrencia": "mensal"
}{ "ok": true,
"url": "https://checkout.stripe.com/…" }
// sem Stripe
{ "ok": false,
"error": "stripe_nao_configurado" }Webhook do Stripe. No evento checkout.session.completed, gera automaticamente uma nova licença. Exige corpo raw e valida a assinatura com STRIPE_WEBHOOK_SECRET.
stripe listen --forward-to localhost:3000/webhook/stripe e copie o whsec_… para o .env.{ "received": true }
Admin
Todas as rotas abaixo exigem o cabeçalho x-admin-pass. Respondem 401 se a senha estiver incorreta.
Valida a senha de administrador.
{ "adminPass": "SUA_SENHA" }{ "ok": true }GET lista todas as licenças. POST cria uma licença (gera codigo automático).
{
"plano": "mensal",
"dias": 30
}{
"ok": true,
"licenca": {
"codigo": "A1B2C3D4",
"plano": "mensal",
"dias": 30
}
}GET lista os usuários (sem a senha). POST cria um usuário já ativo por N dias.
{
"nome": "Lauro",
"email": "lauro@ger.com",
"whatsapp": "5554999480043",
"senha": "••••••",
"segmento": "educacao",
"plano": "Premium",
"dias": 30
}{
"ok": true,
"usuario": { "id": "…",
"email": "lauro@ger.com",
"ativaAte": "2026-08-12T…" }
}GET devolve a configuração global. POST aplica um patch parcial (merge). Campos: logo, status, checkoutUrl, tutoriaisUrl, instalacaoUrl, desinstalacaoUrl, suporteWhatsapp, docUrl, downloadUrl.
{
"config": {
"status": "ativa",
"downloadUrl": "…/wa-eadcom.zip"
}
}{ "ok": true,
"config": { /* config atualizada */ } }