Carrinhos abandonados sem nenhuma tabela nova: HogQL do PostHog
Como o PideAí detecta carrinhos abandonados por loja com uma query HogQL sobre eventos do PostHog: sem tabela, sem cron e sem sincronizar estado.
Alguns meses atrás precisei resolver algo que parecia simples: saber, para cada loja do PideAí, quantos clientes adicionam produtos ao carrinho e nunca finalizam o pedido.
O primeiro instinto foi o de sempre. Uma tabela abandoned_carts, um cron rodando a cada hora, lógica para marcar o carrinho como abandonado depois de X minutos sem pedido, e outro job para disparar o aviso pelo WhatsApp. Arquitetura conhecida, documentada e entediante.
Antes de escrever a primeira linha, me fiz uma pergunta: já tenho essa informação em algum lugar?
Tinha. Fazia meses que o PideAí mandava eventos para o PostHog. Toda vez que um cliente adicionava um produto ao carrinho de uma loja, saía um evento. Toda vez que finalizava o pedido, outro. Os dados já existiam; faltava um jeito de consultá-los juntos.
O que é o PostHog
O PostHog é uma plataforma open source de analytics de produto. Em resumo: ele mostra o que os usuários fazem dentro da sua aplicação.
Cada ação que importa (ver um produto, adicionar ao carrinho, abandonar o checkout, concluir um pedido) vira um evento com suas propriedades. Com isso você monta funis, assiste gravações de sessão e roda testes A/B sem trabalhar em cima de suposições.
A diferença para o Google Analytics é o foco. O Google Analytics diz quantas páginas alguém visitou; o PostHog diz exatamente o que a pessoa fez dentro do seu app.
O que pouca gente sabe, e que é a chave de tudo isso, é que o PostHog também deixa você fazer queries SQL sobre os seus eventos a partir da sua própria aplicação. Eles chamam isso de HogQL.
O custo da solução clássica

Implementar carrinhos abandonados do jeito clássico não é difícil. O custo está no que quase ninguém menciona: a manutenção.
Você precisa manter o estado do carrinho no banco sincronizado com o que acontece no frontend. Se o usuário fecha o navegador, se a sessão expira, se dá erro de rede, a tabela fica desatualizada. Os cron jobs falham em silêncio. E no dia em que você quer um filtro novo, como “carrinhos abandonados acima de R$50”, mexe em queries, migrations e jobs.
Tudo isso para responder uma pergunta que, no fundo, é esta: houve um evento A que não foi seguido por um evento B?
HogQL: SQL sobre os seus eventos
HogQL é o dialeto SQL do PostHog. Ele não serve só para os gráficos da interface: você consulta via API a partir da sua aplicação e usa o resultado como se viesse do seu próprio banco.
No PideAí, em vez de manter uma tabela de carrinhos por loja, escrevi uma query que cruza dois eventos:
WITH cart_events AS (
SELECT person_id, properties.cart_value, timestamp
FROM events
WHERE event = 'product_added_to_cart'
AND properties.store_id = '{storeId}'
AND timestamp >= now() - INTERVAL {days} DAY
),
completed_orders AS (
SELECT DISTINCT person_id, timestamp
FROM events
WHERE event = 'order_placed'
AND properties.store_id = '{storeId}'
AND timestamp >= now() - INTERVAL {days} DAY
)
SELECT
count(DISTINCT c.person_id) as total_abandoned,
sum(toFloat(c.cart_value)) as total_value,
avg(toFloat(c.cart_value)) as avg_cart_value
FROM cart_events c
LEFT JOIN completed_orders o
ON c.person_id = o.person_id
AND o.timestamp > c.timestamp
AND o.timestamp <= c.timestamp + INTERVAL 24 HOUR
WHERE o.person_id IS NULL
A lógica é direta. Pega todos os clientes que adicionaram algo ao carrinho de uma loja e cruza com os que fizeram um pedido nas 24 horas seguintes. Quem não aparece nesse cruzamento é carrinho abandonado.
Sem tabela, sem job e sem nada para sincronizar.

Como fica no código
No React, o hook que alimenta o painel de cada loja ficou assim:
export function usePostHogAbandonedCartStats(storeId: string, days = 30) {
return useQuery({
queryKey: ['abandoned-cart-stats', storeId, days],
queryFn: () => getAbandonedCartStats(storeId, days),
refetchInterval: 5 * 60 * 1000, // atualiza a cada 5 minutos
enabled: !!storeId,
});
}
Agora cada loja vê quantos carrinhos foram abandonados, quanto dinheiro ficou neles, o valor médio e a taxa de recuperação, com dados que se atualizam a cada cinco minutos. E o banco de dados não recebeu nenhuma migration.

O que eu aprendi
Quase nunca falta infraestrutura. O que falta é fazer perguntas melhores para a que você já tem.
Se você já registra eventos com PostHog, Mixpanel ou qualquer ferramenta de analytics, provavelmente estão lá respostas que você procura em outro lugar. A diferença está em a ferramenta deixar você consultá-la a partir do seu app ou só entregar um dashboard fechado.
O PostHog deixa. No PideAí isso abriu uma categoria inteira de funcionalidades que construímos sem tocar no banco principal.
Então fica a pergunta que ficou na minha cabeça: quantas tabelas do seu banco existem só porque você não sabia que dava para consultar esse dado em outra ferramenta?