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 — Defensas
  2. Ejercicio 2 — ACK + cola
  3. Ejercicio 3 — Fuera de orden
  4. Ejercicio 4 — Saliente
  5. Resumen del profesor

Ejercicio 1 — Defensas

  1. Con el endpoint ingenuo, un curl con {"status": "SUCCEEDED"} marca pagos ajenos: hackeado en 30 segundos. La firma es la barrera.
  2. HMAC sobre el RAW body (los bytes exactos): si verificas sobre request.data re-serializado, un cambio de espaciado/orden de claves invalida la firma (o peor, acepta variantes). compare_digest es constante-time: == filtra por corto-circuito y permite timing attacks teóricas.
  3. Firma de otro cuerpo → el HMAC no coincide (el timestamp está dentro del material firmado): 401. Timestamp viejo → 400: un payload interceptado no sirve después de la ventana (5 min: equilibrio entre relojes desincronizados y ventana de ataque; el proveedor firma el timestamp para que no puedas moverlo).

Ejercicio 2 — ACK + cola

  1. Síncrono: 3s+ de respuesta. Con timeout del proveedor de 5s vas al límite; si tu BD se ralentiza, el proveedor marca fallo y reintenta mientras sigues procesando → duplicados y tormenta.
  2. ACK en <100ms: validar + encolar (o spawn de hilo en simulación). El procesamiento real vive en el worker con su propia idempotencia.
  3. exists(gateway_event_id) antes de crear: 1 entrada, 3×200. El duplicado NO es error: es esperado (at-least-once).

Ejercicio 3 — Fuera de orden

  1. Si tu handler aplicara status = payload["status"] a ciegas, el pago confirmado volvería a PENDING: la máquina de estados (00b) lo impide — SUCCEEDED → PENDING no es transición legal y lanza InvalidTransition.
  2. Al evento legal: 200 (procesado). Al ilegal: 200 con "ignored" (log de la transición rechazada) — responder 4xx/5xx provocaría reintentos infinitos del proveedor por un evento que simplemente llegó tarde y no debe aplicarse. Excepción: un evento DESCONOCIDO (formato nuevo que no reconoces) sí merece 4xx para que el proveedor te avise... o 200 con log: depende del proveedor; lo documentas en su checklist (§4).

Ejercicio 4 — Saliente

  1. Tabla de entregas con attempts, next_retry_at y backoff 1m/5m/30m/2h/6h; el worker (o celery beat) consulta "pendientes con next_retry <= now()".
  2. 503 → intento fallido → attempts+1 y next_retry programado; tras 5, FAILED (y alerta/panel: el organizador no recibe y eso es un incidente de soporte, no silencio).
  3. 410 Gone = la suscripción está muerta por decisión del receptor: desactivar el endpoint (flag) y avisar al organizador por otro canal. Reintentar para siempre contra 410 es quemar tu cola — por eso se distingue 5xx (reintenta) de 410 (no reintenta) y 4xx de auth (no reintenta; revisa credenciales).

Resumen del profesor

  • Confianza cero: firma sobre raw body + ventana anti-replay + idempotencia por event_id.
  • ACK rápido + worker: el proveedor reintenta con timeout; tu lentitud se convierte en tormenta.
  • Máquina de estados absorbe el desorden; los eventos ilegales se ignoran con 200 y log.
  • Lo que haces recibir, lo haces enviar: firma, reintentos con backoff, y saber cuándo NO reintentar (410).

Cierre del módulo de APIs. Después: Lección 18 — Sesiones vs tokens (Módulo de seguridad).