Um webhook autenticado por HMAC transforma todo evento dos grupos de WhatsApp em uma linha crua num data lake — pronto para a equipe de dados explorar depois.
Em uma frase
Este PR entrega o lado Laravel do ingest: recebe, valida e persiste eventos crus do Baileys. Sem agregação, sem dashboard — esta é a fase de mapeamento: capturar tudo, decidir o que medir depois.
Cada evento percorre quatro estações. O caminho é fino e rápido na borda (resposta < 50 ms) e o trabalho pesado vai para a fila do Horizon.
Segura a sessão do WhatsApp e envia todos os eventos, sem filtro, para o webhook.
Confere o HMAC-SHA256 do corpo (timing-safe) e exige X-Event-Id. Assinatura ruim → 401.
Valida o corpo (FormRequest), checa duplicata por event_id e despacha o Job. Responde 202 accepted (ou 202 duplicate).
Fora do request: upsert do grupo e do participante, e firstOrCreate(event_id) do evento. Idempotente mesmo sob retry/corrida.
Idempotência em 2 camadas: short-circuit por event_id no controller + firstOrCreate no Job. Reenvio nunca duplica.
Estratégia data lake (schema-on-read): o payload jsonb guarda o evento Baileys cru. Só type, FKs e timestamps viram coluna — para indexar.
Participante é global — 1 linha por número (external_jid UNIQUE), não 1 por grupo. O vínculo com cada grupo vive nos eventos; "em quais grupos a pessoa está" = DISTINCT group_id nos eventos dela. Isso resolve uma contradição interna do spec e mantém identidade ≠ relacionamento.
O monolito já usa uuid em users. UUIDv7 é ordenado por tempo → sem fragmentação de índice no insert da tabela de alto volume.
Integridade + anti-replay (X-Event-Id). Middleware novo — não havia precedente de HMAC no repo.
Bot envia tudo, Laravel salva tudo cru. Some o subsistema de polling/cache e fica mais aderente ao data lake. Revisitar no endurecimento.
type é string, não enumO conjunto de eventos do Baileys é aberto e muda entre versões. Um enum rejeitaria tipos novos e quebraria o mapeamento.
Privacidade — postura consciente
Telefone real (sem hash), conteúdo cru e sem TTL. É a postura mais exposta possível sob LGPD — uma decisão deliberada e temporária da fase de mapeamento, registrada no docs/adr/0001.
Débito a revisitar ao fim da exploração: hashing, minimização de conteúdo e política de retenção. Não é descuido — é escopo.
MODELS · 3
UUID, casts jsonb, FKs nuláveis.
JOB · 4
Upsert grupo+participante, evento sem grupo, idempotência, não-duplicação.
WEBHOOK · 5
HMAC ✓→202, assinatura inválida→401, sem event-id→401, body→422, duplicata→202.