Geração de Resultados API v1 spec.json

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

Base do site  https://www.geracaoderesultado.com.br
Base da extensão  /extensao

Autenticaçã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é.

1 · Extensão  no Chrome do atendente
2 · Token  gerado na própria extensão
3 · Bridge  https://bridge.geracaoderesultado.com.br
PASSO 1Instalar a extensão

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

Uma licença = uma instalação. Ao ativar em outro computador, a extensão avisa e oferece transferir.
PASSO 2Gerar o token

No WhatsApp Web, com a extensão ativa: trilha lateral → API → botão Gerar tokenCopiar. Ligue também Ativar / Desativar API.

O que esperar
CampoComo se comporta
Token de acessoSomente leitura. Quem emite é o servidor — não se digita nem se cola.
URL do bridge / Bridge HTTPChegam prontos do painel. Não são editáveis: são infraestrutura do provedor.
Testar conexãoVerde = servidor respondeu e aceitou o token. Vermelho = recusou.
O token é pessoal e vale por assinatura. Quem o tiver manda mensagem pelo seu número. Gerar um novo invalida o anterior na hora — e quebra as integrações que ainda usam o antigo, então troque de propósito, não por curiosidade.
PASSO 3Conferir se está no ar

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"
Como ler a resposta
RespostaSignifica
{"ok":true,"etiquetas":[…]}Tudo funcionando, ponta a ponta.
nenhuma_extensao_conectadaToken válido — falta o WhatsApp Web aberto, ou a API está desligada na extensão.
401 token_invalidoToken errado, já rotacionado, ou licença vencida.
O nenhuma_extensao_conectada é boa notícia em um primeiro teste: só aparece depois que o token foi reconhecido.
PASSO 4Receber (webhooks)

Para o seu sistema ser avisado das mensagens: trilha → WebHook → informe a URL, marque os eventos e todos os campos em "Dados a enviar".

Marque os campos: só evento e timestamp são garantidos — o resto só chega se estiver marcado. Detalhes do formato na seção Webhooks.
Quando algo não funciona
SintomaOnde olhar
Tudo dá token_invalido/diagnostico na bridge — diz se o segredo dela é aceito pela plataforma
Envio some sem erroConsole do WhatsApp Web (F12, nível Verbose): as linhas começam com [CRM]
Webhook não chegaExtensã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.

Base do bridge  https://bridge.geracaoderesultado.com.br
Autenticação  /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.

POST/api/enviar-texto/{token}

Envia uma mensagem de texto. Também aceita GET com os mesmos campos na query.

Parâmetros
NomeEmDescrição
token *pathToken do assinante, gerado na extensão.
numero *bodyQualquer formatação serve — só os dígitos são usados. Aceita também telefone, phone, number, to.
texto *bodyConteúdo da mensagem. Aceita também mensagem, message, text, body.
Requisição
curl -X POST "https://bridge.geracaoderesultado.com.br/api/enviar-texto/SEU_TOKEN" \
  -H "content-type: application/json" \
  -d '{"numero":"5511988880001","texto":"Olá!"}'
Resposta 200
{ "ok": true }
POST/api/enviar-imagem|video|documento|audio/{token}

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.

Parâmetros
NomeEmDescrição
numero *bodyDestinatário.
url *bodyEndereço público do arquivo. Alternativa: base64.
legendabodyTexto junto da mídia (imagem, vídeo e documento).
nomeArquivobodyNome exibido no documento.
Requisição
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"}'
Resposta 200
{ "ok": true }

WhatsApp — etiquetas e notas

GET/api/listar-etiquetas/{token}

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

Resposta 200
{ "ok": true, "etiquetas": [
  { "id": "1", "nome": "Fazer Matrícula", "cor": "#64C4FF" }
]}
POST/api/modificar-etiquetas/{token}

Aplica ou remove uma etiqueta em um ou vários números.

Requisição
{
  "numeros": ["5511988880001", "5511988880002"],
  "etiquetaId": "1",
  "acao": "adicionar"
}
Resposta 200
{ "ok": true, "resultados": [
  { "numero": "5511988880001", "ok": true }
]}
POST/api/criar-nota/{token}

Cria uma anotação interna no contato. A nota é da extensão — o contato não vê nada.

{ "numero": "5511988880001", "texto": "Ligar amanhã" }
POST/api/apagar-mensagem/{token}

Apaga uma mensagem. Use o dados.id que veio no webhook (sinônimos id, messageId).

NomeDescrição
mensagemId *Id da mensagem a apagar.
paraTodostrue apaga para o contato também (revoke); false (padrão) só do seu lado.
{ "mensagemId": "true_5511999999999@c.us_ABC123", "paraTodos": true }
Revoke só vale para mensagem que VOCÊ enviou e dentro da janela de tempo do WhatsApp. Para mensagem do contato, só 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.

GET/api/listar-grupos/{token}

Lista grupos e comunidades da conta conectada.

Resposta 200
{ "ok": true, "grupos": [
  { "chatId": "1203630000000001@g.us",
    "nome": "Turma 2026",
    "participantes": 42,
    "ehComunidade": false,
    "subgrupos": 0 }
]}
GET/api/membros-grupo/{token}?chatId=…

Participantes do grupo, indicando quem é administrador.

{ "ok": true, "membros": [
  { "numero": "5511988880001", "nome": "Ana", "admin": true }
]}
POST/api/criar-grupo/{token}

Cria um grupo já com os participantes.

Requisição
{ "nome": "Turma 2026",
  "numeros": ["5511988880001", "5511988880002"] }
Resposta 200
{ "ok": true,
  "chatId": "1203630000000001@g.us" }
POST/api/participantes-grupo/{token}

Uma rota para as quatro operações. acao aceita adicionar, remover, promover e rebaixar.

{ "chatId": "1203630000000001@g.us",
  "numeros": ["5511988880003"],
  "acao": "promover" }
GET/api/subgrupos-comunidade/{token}?chatId=…

Os subgrupos vinculados a uma comunidade.

{ "ok": true, "subgrupos": ["1203630000000002@g.us"] }
POST/api/enviar-texto/{token} — para um grupo

Qualquer rota de envio aceita grupo: troque numero por chatId.

{ "chatId": "1203630000000001@g.us",
  "texto": "Aviso para a turma" }

WhatsApp — erros e limites

Erros
RespostaO que houve
401 token_invalidoToken desconhecido, revogado, ou licença vencida.
404 rota_desconhecidaAção que não existe.
405 use_postEssa ação só aceita POST.
nenhuma_extensao_conectadaO WhatsApp Web daquele assinante não está aberto, ou a API está desligada na extensão.
numero_nao_existe_no_whatsappO número não tem conta no WhatsApp.
numero_obrigatorioFaltou o campo. A resposta traz camposRecebidos e camposAceitos para você ver o que chegou.
Limites de tamanho
TipoLimiteObservação
Imagem, vídeo, áudio~16 MBImposto pelo WhatsApp, não pela API.
Documento~2 GBUse enviar-documento para arquivos grandes.
Envio por base64~45 MBO 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.

POST<a URL que você configurou>
Corpo (payload máximo, com todos os campos marcados)
{
  "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.
Campos e o que cada um libera
MarcaçãoCampo no corpo
Dados do eventodados — varia por evento (ver abaixo)
Númeronumero
Nomenome
Fotofoto
Etiquetaetiqueta
Perfil do contatoperfil
Usuário logadousuario — o número que está conectado

Webhooks — eventos e o que vem em dados

EventoQuando disparadados
mensagem_recebidachegou mensagem{ id, texto, tipo, ts, direcao }
mensagem_enviadasaiu mensagemidem, direcao: "enviada"
etiquetaetiqueta aplicada ao contato{ etiquetaId, abaId }
crmlead muda de etapa no funiletapa de origem e destino
respostas_rapidasresposta rápida enviada{ atalho, titulo }
encerrar_atendimentoatendimento encerrado
agendamentomensagem agendada disparou{ texto, id, recorrencia }
auto_atendimentoregra ou IA respondeu{ regraId, via } ou { via: "ia", agente }
follow_uppasso de follow-up executado{ sequencia, passo }
mensagem_apagadamensagem 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.
Dois avisos: 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.
Mídia recebida — buscar os bytes

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:

Em mídia, 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.
GET/api/baixar-midia/{token}?mensagemId=…

Também aceita POST com o mesmo campo no corpo.

Parâmetros
NomeDescrição
mensagemId *O dados.id que veio no webhook. Sinônimos: id, messageId.
limiteMBTeto para este pedido. O padrão e o máximo são 64 MB — pedir mais não aumenta.
Requisição
GET /api/baixar-midia/SEU_TOKEN
      ?mensagemId=ABCD123
Resposta 200
{ "ok": true,
  "base64": "data:audio/ogg;base64,T2dnUw...",
  "mimetype": "audio/ogg; codecs=opus",
  "tamanho": 48213 }
Erros
mensagem_nao_encontradaO id não existe no WhatsApp conectado (mensagem apagada, outro número) ou está fora do formato de id de mensagem.
sem_midia_nesta_mensagemA mensagem existe, mas é texto — não há arquivo para baixar. Vem com tipo.
muito_grandePassa do teto; vem com tamanho e limiteMB.
nenhuma_extensao_conectadaWhatsApp Web fechado — o arquivo mora lá, não no servidor.
Busque sob demanda, não para toda mensagem: o download passa pela extensão e volta em base64. Guarde o dados.id e peça só quando for usar.

Webhooks — entrega e segurança

Cabeçalhos que acompanham todo POST
CabeçalhoPara quê
X-GR-EventoNome do evento — o mesmo que vem em evento no corpo.
X-GR-EntregaIdentificador da entrega. É o mesmo em todas as tentativas — use-o para não gravar o evento duas vezes.
X-GR-Tentativa1 na primeira, 2… nas retentativas.
X-GR-TimestampUnix em segundos de quando foi assinado. Só com chave configurada.
X-GR-Signaturesha256=<hex>. Só com chave configurada.
Assinatura — como conferir

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; }
Sem chave configurada, não vem assinatura. Nesse caso o seu endpoint não tem como provar que o POST veio daqui — ponha ao menos um segredo no caminho da URL (https://seu-sistema.com/webhook/gr/<aleatorio>) e recuse o resto. Preencher a chave é melhor.
Retentativa

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 respondeuO que acontece
2xxEntregue. Fim.
5xx, 429, 408, ou nem respondeuRetenta na escala acima.
Outro 4xxNão retenta. É o seu sistema dizendo "não" — URL errada, token vencido, corpo recusado. Repetir daria a mesma resposta.
Retentativa gera repetição. Se o seu endpoint processou mas a resposta se perdeu no caminho, o mesmo evento chega de novo. Dedupe por 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.

Como testar em 2 minutos

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

GET/extensao/api/config/:installId

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.

Parâmetros
NomeEmDescrição
installId *pathIdentificador único gerado na instalação da extensão.
whatsappqueryNúmero conectado no WhatsApp Web (só dígitos). Usado para vincular a conta.
Requisição
# 
GET /extensao/api/config/abc123?whatsapp=5554999480043
Resposta 200
{
  "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"
}
GET/extensao/api/urls/install/:installId

Registra a instalação e devolve a URL para onde a extensão deve levar o usuário logo após instalar (landing/download).

Resposta 200
{ "success": true, "url": "https://www.geracaoderesultado.com.br/extensao/" }
GET/extensao/api/urls/uninstall/:installId

Chamado quando a extensão é removida. Registra a desinstalação e faz redirect para a página de desinstalação configurada (ou para /).

Resposta
# 302 Redirect

Conta & Licenças

POST/user/register

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.

Corpo
{
  "nome": "Lauro",
  "email": "lauro@ger.com",
  "whatsapp": "5554999480043",
  "senha": "••••••",
  "codigoLicenca": "A1B2C3D4"
}
Resposta 200 / erro 400
{ "ok": true }

// 400
{ "ok": false,
  "error": "licença inválida ou já usada" }
POST/user/login

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.

Corpo
{
  "email": "lauro@ger.com",
  "senha": "••••••"
}
Resposta 200
{
  "ok": true,
  "usuario": {
    "nome": "Lauro",
    "email": "lauro@ger.com",
    "whatsapp": "5554999480043",
    "plano": "Premium",
    "diasRestantes": 24,
    "ativaAte": "2026-08-12T…"
  },
  "downloadUrl": "…/extensao/wa-eadcom.zip"
}
POST/checkout

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.

Corpo
{
  "total": 1079,
  "descricao": "Base + Extensão",
  "email": "lauro@ger.com",
  "recorrencia": "mensal"
}
Resposta 200 / erro
{ "ok": true,
  "url": "https://checkout.stripe.com/…" }

// sem Stripe
{ "ok": false,
  "error": "stripe_nao_configurado" }
POST/webhook/stripe

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.

Configuração local: stripe listen --forward-to localhost:3000/webhook/stripe e copie o whsec_… para o .env.
Resposta 200
{ "received": true }

Admin

Todas as rotas abaixo exigem o cabeçalho x-admin-pass. Respondem 401 se a senha estiver incorreta.

POST/admin/loginadmin

Valida a senha de administrador.

Corpo
{ "adminPass": "SUA_SENHA" }
Resposta
{ "ok": true }
GETPOST/admin/licencasadmin

GET lista todas as licenças. POST cria uma licença (gera codigo automático).

POST — corpo
{
  "plano": "mensal",
  "dias": 30
}
POST — resposta
{
  "ok": true,
  "licenca": {
    "codigo": "A1B2C3D4",
    "plano": "mensal",
    "dias": 30
  }
}
GETPOST/admin/usuariosadmin

GET lista os usuários (sem a senha). POST cria um usuário já ativo por N dias.

POST — corpo
{
  "nome": "Lauro",
  "email": "lauro@ger.com",
  "whatsapp": "5554999480043",
  "senha": "••••••",
  "segmento": "educacao",
  "plano": "Premium",
  "dias": 30
}
POST — resposta
{
  "ok": true,
  "usuario": { "id": "…",
    "email": "lauro@ger.com",
    "ativaAte": "2026-08-12T…" }
}
GETPOST/admin/configadmin

GET devolve a configuração global. POST aplica um patch parcial (merge). Campos: logo, status, checkoutUrl, tutoriaisUrl, instalacaoUrl, desinstalacaoUrl, suporteWhatsapp, docUrl, downloadUrl.

POST — corpo
{
  "config": {
    "status": "ativa",
    "downloadUrl": "…/wa-eadcom.zip"
  }
}
Resposta
{ "ok": true,
  "config": { /* config atualizada */ } }