Guia da integração implementada para enviar leads de um formulário hospedado em uma landing page externa à plataforma Gestão de Tráfego.
Atualmente, as configurações de canal/campanha Ads não oferecem um campo de URL que conecte automaticamente uma landing page ao recebimento de leads. O conector também não visita nem lê a página indicada por uma URL.
É necessário configurar a integração no backend da landing page (ou em uma função serverless): ao receber o formulário, esse backend envia os dados para POST /integracoes/leads, assinando a requisição com o segredo HMAC. Um formulário estático com apenas HTML/JavaScript não pode fazer essa assinatura com segurança, pois o segredo ficaria exposto no navegador. A chamada server-to-server também evita depender de CORS entre os domínios.
O backend deve enviar o campanha_id de uma campanha já existente na plataforma. Não é o ID do canal Ads nem a URL da landing page.
Visitante envia formulário
→ backend da landing page valida os dados
→ backend monta e assina o JSON com FORMULARIO_SECRET
→ POST /integracoes/leads
→ plataforma valida JSON, timestamp e HMAC
→ webhook_eventos + fila "webhooks"
→ ProcessarWebhookJob resolve campanha/cliente/usuário e cria o lead
O endpoint responde 202 Accepted quando o evento for recebido e enfileirado.
O processamento do lead é assíncrono; consulte /admin/webhooks para acompanhar o resultado. Um 202 não garante que o job posterior terminou com sucesso.
Configure um segredo aleatório forte no .env da plataforma:
FORMULARIO_SECRET=use-um-segredo-aleatorio-forte
FORMULARIO_TIMESTAMP_TOLERANCE=300
WEBHOOK_ALLOW_UNSIGNED=false
Configure o mesmo valor de FORMULARIO_SECRET como variável de ambiente privada no backend da landing page. Não o coloque em HTML, JavaScript, repositório público ou parâmetros da URL.
Descubra o campanha_id da campanha existente que receberá esses leads e configure-o no backend da landing page. A plataforma deriva da campanha o cliente e o usuário proprietários.
Configure o backend para enviar o POST para a URL completa da aplicação, terminada em /integracoes/leads (por exemplo, https://app.exemplo.com.br/gestao-trafego/integracoes/leads, conforme a instalação e o URL_BASE).
Garanta que um worker da fila webhooks esteja ativo em produção. VejaWEBHOOKS.md.
Não é necessário configurar a URL da landing page no canal Ads para que o endpoint receba os envios.
POST /integracoes/leads
Content-Type: application/json
X-Formulario-Signature: sha256=<hmac-sha256-em-hex>
A assinatura é sha256= seguido do HMAC-SHA256 do corpo HTTP bruto usando FORMULARIO_SECRET. O corpo enviado deve ser exatamente o mesmo corpo usado para gerar a assinatura; não o reserialize entre a assinatura e o envio.
O JSON exige os campos abaixo:
| Campo | Tipo | Descrição |
|---|---|---|
event_type |
string | Tipo do evento; use lead_form_submit |
id |
string | Identificador único para este envio, usado na deduplicação |
timestamp |
inteiro | Timestamp Unix atual; aceito dentro da tolerância configurada |
campanha_id |
inteiro | ID de uma campanha existente na plataforma |
nome |
string | Nome do lead |
Campos opcionais: email, telefone, utm_source, utm_medium, utm_campaign, utm_content, utm_term, gclid, fbclid e mensagem.
UTMs, identificadores de anúncio e mensagem são preservados nas observações do lead. E-mail e telefone são usados como dados de contato.
Exemplo de payload:
{
"event_type": "lead_form_submit",
"id": "form-8a6c1d00-1f05-4cc2-a8d3-9f157f5d5a88",
"timestamp": 1791484800,
"campanha_id": 42,
"nome": "João Silva",
"email": "joao@example.com",
"telefone": "11999990000",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "campanha-verao"
}
Gere um id novo para cada submissão legítima e reutilize-o apenas ao repetir a mesma tentativa por falha de rede. Se o primeiro evento já estiver processando ou processado, o endpoint retorna 200 already_accepted. Se ainda estiver pendente, o reenvio pode ser aceito com 202 e enfileirado novamente; o job também verifica se já existe um lead com aquele ID antes de criar outro.
Este exemplo representa o handler server-side do formulário. Adapte a leitura dos campos e a validação à landing page; não envie o segredo ao navegador.
<?php
declare(strict_types=1);
$secret = getenv('FORMULARIO_SECRET');
if ($secret === false || $secret === '') {
throw new RuntimeException('FORMULARIO_SECRET não configurado.');
}
$dados = [
'event_type' => 'lead_form_submit',
'id' => bin2hex(random_bytes(16)),
'timestamp' => time(),
'campanha_id' => 42, // ID de campanha existente na plataforma
'nome' => trim((string) ($_POST['nome'] ?? '')),
'email' => trim((string) ($_POST['email'] ?? '')),
'telefone' => trim((string) ($_POST['telefone'] ?? '')),
'utm_source' => trim((string) ($_POST['utm_source'] ?? '')),
];
$corpo = json_encode(
$dados,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);
$assinatura = 'sha256=' . hash_hmac('sha256', $corpo, $secret);
$ch = curl_init('https://app.exemplo.com.br/gestao-trafego/integracoes/leads');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $corpo,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Formulario-Signature: ' . $assinatura,
],
CURLOPT_TIMEOUT => 10,
]);
$resposta = curl_exec($ch);
if ($resposta === false) {
$erro = curl_error($ch);
curl_close($ch);
throw new RuntimeException('Falha ao enviar lead à plataforma: ' . $erro);
}
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 202 && $status !== 200) {
throw new RuntimeException(
'A plataforma recusou o lead (HTTP ' . $status . '): ' . $resposta
);
}
Em produção, não mostre respostas internas ou erros técnicos aos visitantes. Registre-os no log privado do backend e apresente uma mensagem apropriada no formulário. Uma resposta 200 indica que o evento anterior já está processando ou foi processado; 202 indica que este evento foi enfileirado.
| HTTP | Resposta/causa |
|---|---|
202 |
{"ok":true,"status":"accepted","evento_id":...} — evento enfileirado |
200 |
{"ok":true,"status":"already_accepted"} — o evento anterior já está processando/processado |
401 |
Assinatura ausente/inválida ou timestamp fora da tolerância |
422 |
Corpo vazio, JSON inválido ou campo obrigatório ausente |
Um reenvio com o mesmo id enquanto o evento original ainda está pendente pode receber 202 e gerar outro evento na fila. Use sempre o mesmo ID ao repetir a mesma submissão e não crie um ID novo para contornar erros.
Se a requisição receber 202, mas o lead não aparecer, verifique /admin/webhooks, confirme que o worker da fila webhooks está ativo e consulte o erro do evento. Uma campanha inexistente, por exemplo, só é detectada durante o processamento assíncrono e marca o evento como erro.
FORMULARIO_SECRET privado no servidor da plataforma e no backend da landing page; nunca o inclua no JavaScript do cliente.WEBHOOK_ALLOW_UNSIGNED=false em produção.timestamp imediatamente antes do envio e mantenha os relógios dos servidores sincronizados.id único por envio para permitir reenvio idempotente.Para detalhes das demais origens, filas e operação de webhooks, consulte WEBHOOKS.md e API-INTERNA.md.