Módulo 3 · Diseño de APIs

Lección 17 — Webhooks y APIs de terceros

Reintentos, timeouts y firmas: el webhook de la pasarela de pago que confirma tu entrada.

Publicada
En esta lección
  1. Ejercicio 1 — El webhook sin defensas (roto a propósito)
  2. Ejercicio 2 — ACK rápido + procesamiento diferido
  3. Ejercicio 3 — Fuera de orden
  4. Ejercicio 4 — Tu webhook saliente
  5. Ejercicio 6 — La fortaleza del webhook entrante
  6. Entrega

Simulador de pasarela incluido (tu propio script firma y envía). No mires solutions.md hasta entregar.

Ejercicio 1 — El webhook sin defensas (roto a propósito)

  1. Implementa POST /api/payments/webhook/ que confíe en el cuerpo ({"event_id", "payment_id", "status", "timestamp"}) y marque el pago como SUCCEEDED. Pruébalo con curl. ¿Cuánto tardaste en hackearte? (cualquiera con tu URL ya cobró).
  2. Añade firma HMAC-SHA256: X-Signature = hex(hmac(secret, timestamp + "." + raw_body)). Verifica con hmac.compare_digest (¿por qué no ==?).
  3. Ataques a probar: cuerpo válido sin firma → 401; firma de OTRO cuerpo → 401; mismo cuerpo+firma con timestamp de hace 1 hora → 400 (replay). Añade la ventana de 5 min.

Ejercicio 2 — ACK rápido + procesamiento diferido

  1. Simula la cola: process_gateway_event(payload) que tarda 3s (sleep) + crea la entrada. Primero síncrono: mide el tiempo de respuesta del webhook con curl (¿cuánto? ¿qué haría la pasarela con 5s de timeout?).
  2. Envuelve en transaction.on_commit + cola simulada (thread de fondo o delay() si ya montaste Celery). Responde 200 en <100ms y procesa igual.
  3. Demuestra idempotencia: mismo event_id 3 veces → 1 entrada creada, 2 respuestas 200.

Ejercicio 3 — Fuera de orden

  1. Envía payment.succeeded y DESPUÉS payment.pending del mismo pago. ¿Tu handler regresa el pago a PENDING? (si sí: bug de máquina de estados — el confirm()/transiciones de la 00b deben rechazar la transición ilegal).
  2. ¿Qué status respondes a cada uno? (el ilegal: ¿400/409/200-ignorado? Justifica contra la política de reintentos del proveedor).

Ejercicio 4 — Tu webhook saliente

  1. Crea notify_organizer(event_id, payload): firma igual que recibes, POST a una URL de test (requestbin o tu propio endpoint de prueba), reintentos con backoff 1m/5m/30m/2h/6h (tabla outgoing_webhook_delivery(attempts, next_retry, status)).
  2. Simula la URL caída (503): verifica que reintenta según el schedule y que tras 5 intentos queda marcado FAILED para revisión manual.
  3. ¿Qué haces con una URL que responde 410 Gone permanentemente? (pista: no reintentes para siempre — desactiva suscripción y notifica).

Ejercicio 6 — La fortaleza del webhook entrante

  1. Implementa la checklist del §6 sobre tu endpoint de la pasarela fake: las 7 piezas, cada una con su test (verificación, ventana, dedup, ACK+cola, rate limit, filtrado del secret en logs, métrica de firma inválida).
  2. El ataque de replay: captura un webhook válido y reenvíalo tal cual a los 10 min: ¿lo rechaza la ventana? Y a los 2 min con otro event_id: ¿lo acepta el dedup o lo duplica? Documenta los dos límites.
  3. El outbox de salientes: migra los webhooks salientes del 17 al mecanismo outbox→poller (25): test de atomicidad (transacción fallida → cero webhooks encolados) y del reintentante (pasarela ajena caída → backoff → convergencia).

Entrega

Pega código, curl outputs y la tabla de reintentos. Cierra el módulo 3; después Lección 18 — Sesiones vs tokens.