Skip to content

@audit — Webhook MP classificando status normal como incidente

Data: 2026-05-07 Escopo: @audit — banner de incidentes do Dashboard Admin Severidade: baixa (ruído operacional — não afeta funcionalidade nem dados)

Sintoma

Após gerar um boleto via Mercado Pago, o banner do Dashboard Admin mostrava:

⚠ 1 incidente nas últimas 24h — último: pagamento.webhook_validation_failed

Apenas a geração do boleto, sem nenhuma falha técnica real, disparava "incidente".

Causa raiz

supabase/functions/mercadopago-webhook/index.ts validava 4 condições no payload do MP (status_approved, external_reference_match, valor_match, moeda_correta) e, se qualquer uma falhasse, registrava pagamento.webhook_validation_failed.

O MP envia webhook imediatamente após a criação da preferência com status=pending (boleto aguardando pagamento). Isso é o estado normal do ciclo de vida — só vira approved quando o pagador efetivamente paga. Como status_approved=false, o log virava "validation failed" e a ação está no SSOT _shared/incident-actions.ts, alimentando o banner.

Correção

  1. Split de classificação no webhook (L291–380):

    • Se a única falha é status_approved=false E o payment.status está em {pending, in_process, cancelled, refunded} → registra pagamento.webhook_status_recebido (informativo, fora do SSOT de incidentes).
    • Em qualquer outro cenário (referência adulterada, valor divergente, moeda errada, status rejected/charged_back) → mantém pagamento.webhook_validation_failed como incidente legítimo.
  2. SSOT de log actions (src/constants/log-actions.ts): nova entrada "Status de Pagamento Recebido (MP — informativo)" para o filtro do Admin → Logs.

  3. Política de incidentes (docs/operations/INCIDENT_POLICY.md §3): linha explícita listando o ciclo de vida normal do MP como não-incidente.

  4. Comentário em _shared/incident-actions.ts documentando a regra ao lado da exceção análoga de login.falha_senha.

Visibilidade ao usuário

A informação de "boleto pendente / em processamento" continua disponível no histórico da fatura (drawer Admin → Financeiro), que consome logs_auditoria filtrando por pagamento.*. Não há perda de visibilidade — apenas deixa de poluir o banner de incidentes do Dashboard.

Arquivos tocados

  • supabase/functions/mercadopago-webhook/index.ts
  • supabase/functions/_shared/incident-actions.ts
  • src/constants/log-actions.ts
  • docs/operations/INCIDENT_POLICY.md
  • mem://features/admin/dashboard-layout-v2

Não-objetivos

  • UI do banner / dashboard inalterada.
  • Webhook continua logando webhook_status_recebido para auditoria — só muda a classificação de incidente vs informativo.