Vexo perdeu 83,6% dos leadgens em silêncio por 3 dias. Aqui vão essa e mais quatro pegadinhas servindo PostepTrack em 4 clientes.
Sleep 10/15s · HTTP 204 trap · try/catch no waitUntil · System User Token · utm_campaign tem dono
Cliente: Vexo. Branch: leadgen (Lead Ads do Facebook/Instagram). Período: três dias antes da gente perceber. Resultado: 83,6% dos leads passavam pela Edge Function, eram processados “com sucesso” pela Meta, e simplesmente sumiam sem chegar no Kommo. Sem erro no log. Sem alerta. Só lead que não voltava.
Quando achamos o motivo, eram dois bugs aninhados num único bloco try. Bem manso de corrigir, devastador de não ver. Esse post junta esse caso e mais quatro que aprendemos servindo PostepTrack em quatro clientes diferentes.
Se você está construindo (ou contratando) atribuição Meta Ads → CRM, vale ler até o fim. Cada armadilha custou tempo de produção pra gente. Nenhuma está em documentação oficial.
A Meta entrega o webhook em segundos. O CRM (Kommo, no nosso caso) cria o lead a partir da mensagem do WhatsApp ou do Lead Ad com alguns segundos de atraso adicional — e demora ainda mais pra o lead aparecer no GET /leads. Se você fizer o lookup imediatamente, recebe vazio. E vazio aqui é igual a “lead inexistente”.
Esses números não são chute. Saíram de medir taxa de sucesso em produção — com 5s rola muito não-encontrado, 10s estabiliza pra CTWA, 15s pra leadgen (porque o lead Ads passa pelo crawler do Facebook antes). Edge Function Supabase precisa de EdgeRuntime.waitUntil pra não bloquear o response do webhook nesse sleep.
Regra: meça o tempo até o lead estar searchable no SEU CRM. Não use defaults. E nunca faça sleep dentro do handler do webhook sem fire-and-forget — Meta vai chamar de novo achando que você caiu.
Quando você consulta o Kommo procurando um lead por telefone e ele não existe, a API responde com HTTP 204 (No Content). Não 404. Não 200 com array vazio. Só 204 sem corpo.
Esse foi o segundo dos dois bugs aninhados no Vexo. r.ok é true pra qualquer status 2xx, incluindo 204. Quando você tenta dar parse no corpo vazio, vem SyntaxError. Sem try/catch externo, vira silent fail. Vezes 83,6% dos leadgens em três dias.
Regra: trate cada status code que o CRM realmente devolve. 204 não é exceção — é resposta legítima de “não achei”, e seu código tem que saber lidar.
A Meta espera resposta 200 em poucos segundos. Se você demorar (porque tá esperando o sleep do CRM, fazendo lookup, fazendo PATCH), a Meta dá retry — e seu lead vira duplicado, ou pior, fica em loop infinito de webhook.
A solução é fire-and-forget: você responde 200 imediatamente e processa o resto em background. No Supabase, isso é EdgeRuntime.waitUntil(asyncTask()). Funciona — mas tem um detalhe que ninguém te avisa.
Sem o try/catch externo, qualquer exception dentro do waitUntil vira silent fail. O response 200 já voltou, a Meta tá feliz, mas o processamento morreu em silêncio. Combine isso com o bug do 204 e você tem a história do Vexo.
Regra: tudo que roda em fire-and-forget precisa de catch externo que escreva numa tabela. Sem isso, você está confiando que nada vai dar errado — e tudo dá errado em produção.
A documentação da Meta empurra você pro token de usuário, que expira em 90 dias. Toda renovação é manual — alguém precisa lembrar de logar no Business Manager, gerar token novo, atualizar o env, redeploy. Em quatro clientes, isso é uma armadilha cronológica garantida.
System User Token exige uma permissão específica pra ler campos do leadgen — leads_retrieval. Sem ela, você consulta o lead pelo leadgen_id, recebe 200 com payload vazio, e fica horas debugando seu código que estava certo desde o começo.
Regra: na hora de ir pra produção, crie System User no Business Manager do cliente, dê leads_retrieval explícito, e use esse token. Esqueça o de 90 dias — ele é pra protótipo.
Você implementou tudo bonito, o lead chega no Kommo, e quando você popula utm_campaign com o campaign_name vindo da Meta… alguém do marketing reclama que o filtro do dashboard parou de funcionar. Porque utm_campaign já era preenchido por outro sistema upstream — a landing, o formulário, um N8N.
Em 2 dos 4 clientes que rodamos, utm_campaign já tinha proprietário upstream. PostepTrack agora pula esse campo nesses casos e mapeia campaign_name pra um campo dedicado. Pequeno detalhe, mas evita conversa difícil com o marketing três meses depois.
Regra: pra cada UTM que você vai escrever no CRM, pergunte se já tem dono. Atribuição é colaborativa — vários sistemas escrevem no mesmo lead. Sobrescrever sem perguntar quebra o trabalho de quem chegou antes.
Meça o tempo até o lead ser searchable no CRM. 10s pra CTWA, 15s pra leadgen. Sempre fire-and-forget pra Meta não dar retry.
204 é resposta legítima de “não achei”. r.ok é true em 204. Trate explicitamente antes de dar parse no body.
Tudo em fire-and-forget precisa de try/catch externo que persiste a exception numa tabela. Sem isso, exception = silent fail.
Produção pede System User Token (não expira) + permissão leads_retrieval pra Lead Ads. Token de 90d é só pra protótipo.
Pergunte se já tem dono upstream. Sobrescrever campo de UTM quebra dashboard de quem chegou antes — e ninguém percebe na hora.
Atribuição Meta → CRM tem cinco furos onde leads somem em silêncio. Cada um custa tráfego pago real até você ver. Tester só com lead de produção e log que captura exception — não tem outro jeito.