O contexto
A He4rt quer enxergar o engajamento dos membros nos grupos do WhatsApp, do mesmo jeito que já faz no Discord. São dois processos separados, como no Discord o runtime e o ingest são apartados:
Segura a sessão Baileys (WhatsApp Web), filtra na borda e encaminha o evento cru. É a ponte — quase "burro".
Monolito Laravel. Valida, sanitiza e grava cru numa tabela única (whatsapp_event_logs).
O fluxo, ponta a ponta
Um evento atravessa três zonas — bot, rede, backend — até virar uma linha no lake. O pulso desce o trilho no sentido do fluxo.
Idempotência em dose dupla
O event_id é um UUIDv5 determinístico por conteúdo, calculado no bot. Mesmo evento lógico → mesmo id. Isso arma a dedup duas vezes:
event_id é PK do outbox SQLite. INSERT OR IGNORE descarta re-emit idêntico antes de ir à rede.
event_id é UNIQUE. firstOrCreate ignora duplicata se a resposta se perdeu e o bot reenviou.
O ID aleatório (decisão revertida) fazia todo re-emit do Baileys — reconexão, append offline, re-snapshot de groups.metadata a cada open — virar linha nova. Re-emit não é raro: é recorrente.
Uma reação no grupo, calculada de verdade
Chave da reação = (tipo, id da msg, quem reagiu, emoji). O senderTimestampMs fica de fora (instável entre re-emissões). Clique nos emojis e veja o lake reagir:
Chave determinística por tipo
| tipo | chave canônica |
|---|
"canônico" = JSON com chaves ordenadas e participants ordenados por id — senão cada reconexão reordena e o snapshot vira "novo", derrotando a dedup.
As decisões — e os trade-offs
Cada card mostra a opção escolhida, a descartada, o porquê e o custo aceito.
O que salvamos — e o que não
A regra é default-allow com denylist: passa o que é de grupo (@g.us) e não está na lista de corte. "Importante" = tudo que não é o ruído conhecido.
Salvos
passam o filtroNão salvos
cortados — com o motivoFirehoses são cortados por volume altíssimo × valor de coleta baixo (veja as barras). A sessão, por segurança. DMs, por escopo. O resto passa cru.
E quando algo falha?
Seis cenários, o caminho de cada um e o veredito. Em todos: nada importante se perde.
O que ganhamos
- ✓ Sem perda e sem duplicatas — dedup dupla + 2xx-pós-commit + tolerância a poison + dead-letter.
- ✓ Backend mais simples — o job assíncrono morreu; ingest é uma Action síncrona fina.
- ✓ Sem mudança de schema — event_id uuid UNIQUE já comporta UUIDv5.
- ✓ Resistência independe do driver de fila (Redis é app-wide, não do lake).
- ○ Anti-replay, throttle, limite de corpo, rotação de segredo.
- ○ Backfill profundo de histórico (limitado no dispositivo acompanhante).
- ○ Deploy/hospedagem, vinculação de identidade, métricas.
- ○ Governança/LGPD e retenção.