APIs idempotentes que sobreviven a reintentos: guía práctica para backend developers
Aprende a diseñar APIs idempotentes que sobreviven a reintentos sin crear pagos, pedidos o jobs duplicados. Patrones prácticos, ejemplos y trade-offs.
José Miguel Fernández · Software Engineer · 6+ años de experienciaEn este artículo Sección actual: Qué significa idempotencia de verdad
Si tu API recibe reintentos, tus decisiones de diseño importan bastante más que en un demo feliz donde todo sale a la primera.
Una petición que llega una vez es fácil. Una petición que llega dos veces porque el cliente ha hecho timeout, la red ha parpadeado o un worker ha petado ya es otra cosa. Ahí un endpoint aparentemente inocente puede crear pagos duplicados, pedidos duplicados, emails duplicados o jobs duplicados.
La idempotencia existe para ese hueco.
La versión corta: una API idempotente le da al cliente una forma segura de repetir una petición sin cambiar el resultado más de una vez.
Qué significa idempotencia de verdad
En lenguaje normal, idempotencia significa esto:
- enviar la misma petición varias veces lleva al mismo resultado final
- los reintentos no crean efectos secundarios duplicados
- el servidor puede reconocer que ya ha procesado esa operación
Eso no significa que todas las respuestas sean idénticas, ni que todos los métodos HTTP sean automáticamente seguros para repetir de la forma que tu negocio necesita.
El problema aparece sobre todo con POST, porque POST suele crear algo o disparar trabajo. Si el cliente reintenta esa petición después de un timeout, el servidor puede no saber si el primer intento llegó a completarse.
Ahí entra la idempotency key.
Por qué esto importa en sistemas reales
Los fallos típicos son aburridos y caros:
- un pago se cobra dos veces
- un pedido se crea dos veces
- un handler de webhook se ejecuta dos veces
- un job en background se agenda dos veces
- un usuario recibe dos emails de confirmación
Ninguno de esos bugs parece dramático en una revisión de código. Aparece después, cuando la red se porta mal o un cliente upstream reintenta con demasiada alegría.
Yo me lo imagino así:
el cliente envía la petición
↓
el servidor empieza a trabajar
↓
hay timeout en la red
↓
el cliente reintenta
↓
el servidor tiene que decidir:
- procesar otra vez
- o devolver el resultado anterior
Si tu API no sabe responder bien la segunda vez, en realidad no es fiable. Es una apuesta.
Un patrón práctico que sí funciona
El patrón más limpio es este:
- el cliente genera una clave de idempotencia
- el cliente la envía con la petición
- el servidor guarda la clave junto al resultado de la operación
- si vuelve a llegar la misma clave, el servidor devuelve el resultado original
La clave no es un header random más. Forma parte del contrato entre cliente y servidor.
Un flujo mínimo sería algo así:
POST /payments
Idempotency-Key: 9f1b2c...
1ª petición:
- validar entrada
- crear el pago
- guardar la respuesta bajo esa clave
- devolver éxito
2ª petición con la misma clave:
- detectar duplicado
- devolver el resultado guardado
- no cobrar otra vez
Lo importante no es el header en sí. Lo importante es que el servidor controle el deduplicado.
Qué deberías guardar
Como mínimo, guarda suficiente información para contestar al retry de forma segura.
Normalmente eso significa:
- la clave de idempotencia
- la huella de la petición
- el estado de la operación
- el cuerpo de la respuesta o una referencia al recurso creado
- marcas de tiempo para limpiar datos antiguos
- el usuario o tenant, si aplica
No guardes solo la clave y cruces los dedos. Si dos peticiones distintas reutilizan la misma clave, necesitas una forma de detectar el conflicto.
Los trade-offs que casi nadie cuenta
La idempotencia parece sencilla hasta que llegan los bordes.
1. Los replays tienen que estar controlados
Si guardas las claves para siempre, el almacenamiento crece y se llenan de basura histórica. Si las borras demasiado pronto, los reintentos se cuelan.
2. Misma clave, payload distinto
Si un cliente reutiliza una clave con un body diferente, necesitas una política clara:
- rechazarlo
- o tratarlo como conflicto
No lo aceptes en silencio.
3. Efectos secundarios fuera de la base de datos
Si tu endpoint envía un email, mete algo en una cola o llama a otro servicio, también tienes que pensar en eso. Guardar solo el resultado en BD no basta si el side effect ya ocurrió.
4. No todos los reintentos deben ocultarse
Hay reintentos que deberían fallar con ruido. Si la petición está mal formada o no está autorizada, la idempotencia no debería convertir eso en un éxito silencioso.
Qué haría yo en la práctica
Si hoy tuviera que diseñar un endpoint backend, seguiría este checklist:
- hacer la operación explícita y pequeña
- exigir una clave de idempotencia para acciones no repetibles
- persistir la clave y el resultado
- deduplicar antes de ejecutar side effects
- devolver la respuesta original en el retry
- rechazar conflictos entre clave y payload
- expirar claves antiguas con una política razonable
- loguear suficiente contexto para trazar luego la petición
Regla práctica: si una petición duplicada te puede hacer daño, haz que duplicarla sea imposible o inocuo.
La idempotencia no sustituye un buen diseño HTTP
Esta parte se rompe a menudo.
La idempotencia ayuda con los reintentos. No arregla:
- un modelado pobre de recursos
- códigos de estado confusos
- errores inconsistentes
- contratos vagos
- falta de observabilidad
Sigues necesitando buenos endpoints, respuestas predecibles y documentación clara.
Formatos como RFC 9457 / Problem Details pueden ayudar, porque hacen que los fallos sean más fáciles de parsear para el cliente y más fáciles de depurar para humanos. Pero eso es otro problema. La idempotencia va de evitar side effects duplicados.
Cierre
La idempotencia es uno de esos temas backend que parecen pequeños hasta que te ahorran un bug muy caro.
Si tu API acepta reintentos, diseña pensando en ellos desde el principio. No hagas como si no fueran a pasar. Van a pasar. Y cuando pasen, tu sistema debería devolver el mismo resultado o fallar de una forma que sea segura de reintentar.
Si estás diseñando un backend que va a producción, este tema conecta con Spring Boot en producción y con el servicio de integraciones API, donde los reintentos y errores visibles son parte del contrato.
Mejor eso que descubrir cobros duplicados a las 2 de la mañana.
Fuentes y referencias
Aplicación en un sistema real
¿Necesitas revisar este problema en producción?
Puedo analizar la arquitectura, los riesgos y el siguiente cambio útil sin convertir la solución en un proyecto más grande de lo necesario.