nomada-saas feat/billing-webhook
BUG_FIXING
📅 2026-06-12 14:37 · 👤 Rodrigo Heredia (CTO) · 📁 3 files changed · +223 / -46 · 🔀 Stripe webhook · invoice.payment_succeeded
Signal level HIGH — Bug con diagnóstico de causa raíz + decisión de arquitectura
Layer 02 · WorkIntentClassifier Layer 02 · ADHD tree-of-thought · 4 frames
Frame A
Technical
0.8
3 files · lógica de validación de firma refactorizada
Frame B
Uncertainty
0.5
Consulta sobre arquitectura retry + duda inicial sobre el bug
Frame C
Fork
1.0
Opción A (Redis idempotency) vs Opción B (SQS queue)
Frame D
AI Contribution
1.0
Claude identificó causa raíz (raw buffer vs JSON) que el developer no había visto
{
  "frames": {
    "technical": 0.8,
    "uncertainty": 0.5,
    "fork": 1.0,
    "ai_contribution": 1.0
  },
  "pruned": [],
  "intent": "BUG_FIXING",
  "signal": "HIGH",
  "calibration_note": "BUG_FIXING special rule applied: root cause diagnosed ('raw body buffer vs JSON') + fix rationale provided. Frame C elevated by architecture fork (A vs B retry strategy)."
}
Decisiones registradas 2 recorded
Webhook signature validation: raw buffer vs JSON-parsed body
2026-06-12
Root cause
Express parseaba el body como JSON antes de que llegara al middleware de Stripe. stripe.webhooks.constructEvent() recibe un string/object en lugar del raw Buffer, invalidando la firma HMAC silenciosamente — sin excepción, sin log, sin actualización de BD.
Symptom
Webhooks de invoice.payment_succeeded llegaban al endpoint (status 200) pero el estado de suscripción en BD nunca cambiaba.
Fix
Reordenar middlewares: Stripe raw body parser aplicado antes de express.json() en la ruta /webhook/stripe. Usar express.raw({type: 'application/json'}) exclusivamente para esa ruta.
AI contribution
IDENTIFIED Causa raíz exacta (raw buffer vs JSON) — el developer no había vinculado el orden de middlewares con el fallo silencioso
SUGGESTED Diagnóstico "revisa si estás pasando req.body como string o buffer" — directo al problema sin iterar
DEV-DRIVEN Rodrigo identificó que había un fallo, estructuró el contexto del bug y aplicó el fix
Intent class
BUG_FIXING · Signal score HIGH
Outcome
Implemented Zero incidencias en 4h post-deploy

Estrategia de retry para webhooks: single-endpoint vs cola dedicada
2026-06-12
Context
Con el bug resuelto, Rodrigo planteó cómo gestionar reintentos automáticos de Stripe para garantizar idempotencia en pagos duplicados.
Decision
Opción A: mismo endpoint + idempotency key en Redis (stripe_event_id como clave)
Alternatives
Opción B: endpoint separado /webhook/retry con cola SQS + dead-letter queue — descartada por overhead operacional innecesario a escala actual (<1000 clientes)
Reasoning
inferred: A es suficiente para <1000 clientes y migrar a B es straightforward cuando se alcancen 10K+. El criterio de escala fue propuesto por Claude y aceptado por el developer.
AI contribution
IDENTIFIED Trade-off entre complejidad operacional y robustez — el developer no había articulado el criterio de escala
SUGGESTED Umbral de migración (1K → 10K clientes) como criterio de decisión objetivo
DEV-DRIVEN Rodrigo tomó la decisión final y también preguntó dónde colocar el rate limiting (antes/después de validación)
Intent class
BUG_FIXING (runner-up: EXPLORING) · Signal score HIGH
Outcome
Implemented Redis idempotency key activo en producción
Session narrative
What shipped
  • stripe.ts refactorizado con raw body buffer y handler extraído a módulo separado (+87/-43)
  • 124 líneas de tests nuevos cubriendo firma válida, firma inválida, retry idempotente
  • Redis idempotency check integrado en el handler
  • Rate limiting posicionado post-validación de firma
What was figured out
  • El orden de middlewares de Express es crítico para webhooks con firma HMAC
  • Stripe falla silenciosamente si el payload no es raw buffer — sin excepción explícita
  • El criterio de escala (1K/10K clientes) como heurística para elegir arquitectura de retry
  • Rate limiting debe aplicarse después de la firma para no desperdiciar slots con peticiones maliciosas
Where it got hard
Bug invisible en producción: los 200 OK ocultaban el fallo — no había error en logs, solo silencio en BD
Rodrigo no conectó el orden de middlewares con el fallo hasta que Claude señaló el raw buffer
Incertidumbre arquitectural sobre retry: dos opciones válidas sin criterio claro inicial
Next steps inferred
  • Monitoring activo del rate limiter (alertas si se acerca al límite)
  • Revisar otros webhooks (checkout.session.completed, subscription.updated) por el mismo patrón de middleware
  • Documentar el umbral de migración a SQS en el ADR del proyecto
  • Añadir test de integración con Stripe CLI para simular reintentos en staging
AI contribution summary

Claude aportó valor diferencial en dos momentos clave de esta sesión. Primero, identificó la causa raíz del bug crítico de forma inmediata y precisa — el fallo del raw body buffer en la validación de firma de Stripe es un error conocido pero no obvio para quien no ha depurado webhooks en Express antes. Sin esta intervención, el developer habría invertido tiempo adicional iterando sobre síntomas (estado de BD, logs de Stripe) sin atacar la causa real.

Segundo, en la decisión de arquitectura de retry, Claude estructuró la comparativa A vs B con un criterio de escala concreto (umbral 1K/10K clientes) que convirtió una decisión ambigua en una heurística operacionable. El tercer aporte (rate limiting post-firma) fue menor pero evitó un anti-patrón de seguridad.

Frame D score: 1.0Calibración: el developer aportó contexto del bug, tomó decisiones finales y escribió los tests. Claude no generó código — su aportación fue diagnóstica y arquitectural. La sesión no habría concluido sin la intervención en la causa raíz.

Token usage
38.2K
Total tokens
24.1K
Cache read
63%
Cache hit rate
12
Turns
Cache efficiency 63% hit rate · ahorro estimado ~$0.18
Top turns por tokens de entrada
#18,420"Los webhooks de Stripe no están actualizando el estado. El log dice que llegan los eventos..."
#25,180"¿debería manejar el retry automático de Stripe en el mismo handler o crear un endpoint separado?"
#33,290"el rate limiting de los webhooks, ¿lo pongo antes o después de la validación de firma?"

Optimización sugerida: el contexto del proyecto (src/webhooks/) se recarga en cada turno. Añadir CLAUDE.md con el mapa de middlewares podría subir el hit rate al 75-80%.

WORKLOG last 4 entries
... entradas anteriores omitidas ...
2026-06-10 09:15 FEATURE_BUILDING HIGH D:0.6 · cache:71% · tok:52K — Implementado dashboard de MRR con Recharts — primera visualización de métricas de facturación
2026-06-11 11:40 REFACTORING MEDIUM D:0.3 · cache:58% · tok:18K — Extraído cliente de Stripe a módulo compartido — preparación para integración de webhooks
2026-06-12 09:22 FEATURE_BUILDING MEDIUM D:0.3 · cache:55% · tok:14K — Endpoint /webhook/stripe creado con estructura básica y tipos de evento — sin lógica de validación aún
2026-06-12 14:37 BUG_FIXING HIGH D:1.0 · cache:63% · tok:38K — Resuelto bug crítico de validación de firma Stripe (raw buffer) + arquitectura de retry idempotente elegida — desbloqueó billing en producción