Webhooks

Recepção de eventos externos de Google Ads, Meta Ads e formulários próprios, com processamento assíncrono de leads e registro de conversões offline.

Endpoints

Origem Método Rota
Google Ads POST /webhooks/google
Meta Ads (verificação) GET /webhooks/meta?hub.mode=subscribe&hub.verify_token=...&hub.challenge=...
Meta Ads (eventos) POST /webhooks/meta
Formulário próprio POST /integracoes/leads

Esses endpoints públicos não usam CSRF. O formulário próprio autentica as requisições com HMAC; não é necessário nem suficiente cadastrar a URL da landing page em um canal Ads.

Segurança

Formulário próprio

O endpoint POST /integracoes/leads recebe envios do backend da landing page e encaminha-os para o mesmo armazenamento de eventos e fila usados pelos webhooks. A implementação não busca a página a partir de uma URL informada no canal Ads. A landing page precisa ter código server-side (ou uma função serverless) que receba o formulário, gere o JSON, assine o corpo bruto com FORMULARIO_SECRET e envie o POST ao endpoint.

O JSON exige event_type, id, timestamp (Unix), campanha_id e nome. id deve ser único por envio. campanha_id precisa existir; durante o processamento, o sistema resolve o cliente e o usuário pela campanha e cria o lead com origem formulario_proprio. E-mail, telefone, UTMs, gclid, fbclid e mensagem são opcionais. A assinatura deve ser criada no servidor: nunca coloque o segredo no HTML ou JavaScript do navegador.

Respostas principais:

HTTP Significado
202 Evento autenticado e enfileirado; o lead ainda será processado
200 Evento anterior já está processando/processado (already_accepted)
401 Assinatura ausente/inválida ou timestamp fora da janela
422 Corpo vazio, JSON inválido ou campo obrigatório ausente

Um reenvio feito enquanto o primeiro evento ainda está pendente pode receber 202 e enfileirar outro job; 200 already_accepted é retornado quando o evento existente já está processando ou foi processado. O job confere se o lead com aquele ID de formulário já existe antes de criar outro.

Um 202 confirma o recebimento/enfileiramento, não o sucesso final de criação do lead. Verifique o resultado em /admin/webhooks; o job pode falhar depois da resposta, por exemplo, se campanha_id não existir.
Veja CONECTOR-FORMULARIO-PROPRIO.md para configuração, payload e exemplos de código.

Fluxo

1. Google/Meta envia POST → /webhooks/{origem}; ou o backend do formulário envia POST → /integracoes/leads
2. Controller valida a requisição e a assinatura HMAC (o formulário também valida o `timestamp` do JSON)
3. Persiste em webhook_eventos (status=recebido)
4. Enfileira ProcessarWebhookJob na fila 'webhooks'
5. Worker (bin/console queue:work webhooks) processa
6. Job cria/atualiza lead conforme tipo de evento, incluindo formulário próprio
7. webhook_eventos.status vira 'processado' ou 'erro'

Fila

Rodar o worker

Uma vez (teste):

php bin/console queue:once webhooks

Contínuo (produção):

php bin/console queue:work webhooks

No cron (recomendado em hospedagem compartilhada sem systemd):

* * * * * cd /caminho && php bin/console queue:once webhooks >> logs/queue.log 2>&1

Como serviço systemd (VPS):

# /etc/systemd/system/gt-queue.service
[Unit]
Description=Gestão de Tráfego — Queue Worker
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/gestao-trafego
ExecStart=/usr/bin/php bin/console queue:work webhooks
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Monitoramento

Simulação local

# Enviar webhook de teste (dev, sem assinatura)
curl -X POST http://localhost/gestao-trafego/webhooks/google \
    -H "Content-Type: application/json" \
    -d '{"event_type":"conversion","gclid":"abc123","cliente_id":1,"full_name":"Teste"}'

Se WEBHOOK_ALLOW_UNSIGNED=true (dev), o evento é aceito.

Prune

php bin/console queue:prune 7      # remove jobs de +7 dias
# em produção, adicione em cron semanal