Ejercicio 1 — Defensas
- Con el endpoint ingenuo, un curl con
{"status": "SUCCEEDED"}marca pagos ajenos: hackeado en 30 segundos. La firma es la barrera. - HMAC sobre el RAW body (los bytes exactos): si verificas sobre
request.datare-serializado, un cambio de espaciado/orden de claves invalida la firma (o peor, acepta variantes).compare_digestes constante-time:==filtra por corto-circuito y permite timing attacks teóricas. - 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
- 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.
- ACK en <100ms: validar + encolar (o spawn de hilo en simulación). El procesamiento real vive en el worker con su propia idempotencia.
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
- 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 → PENDINGno es transición legal y lanza InvalidTransition. - 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
- Tabla de entregas con
attempts,next_retry_aty backoff 1m/5m/30m/2h/6h; el worker (o celery beat) consulta "pendientes con next_retry <= now()". - 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).
- 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).