21 sept 2025·6 min de lectura

Idempotencia en la API: evita creaciones duplicadas con un diseño seguro ante reintentos

La idempotencia en APIs ayuda a prevenir creaciones duplicadas y cargos dobles usando IDs de petición, restricciones únicas y reglas de reintento seguras para tus endpoints.

Idempotencia en la API: evita creaciones duplicadas con un diseño seguro ante reintentos

Por qué ocurren creaciones duplicadas en la vida real

Las creaciones duplicadas normalmente no son culpa de "usuarios malos". Ocurren porque las redes reales y las personas reales son desordenadas.

Un teléfono con Wi‑Fi inestable envía una petición, la app muestra un spinner y el usuario vuelve a pulsar el botón. O la petición llega a tu servidor, pero la respuesta nunca regresa, así que el cliente reintenta.

Los reintentos también ocurren automáticamente. Apps móviles, librerías fetch del navegador, gateways de API y runners de trabajos en segundo plano suelen reintentar tras un timeout, una conexión perdida o un error 502/503. Desde el punto de vista del cliente, reintentar es la decisión segura: "no estoy seguro de que funcionó, así que lo intentaré de nuevo."

En la práctica, una "creación duplicada" se ve así:

  • Dos pedidos creados por un mismo checkout
  • Dos tickets de soporte por una misma queja
  • Dos suscripciones o cargos por un mismo clic
  • Dos invitaciones enviadas a la misma persona

Lo alarmante es que el servidor a menudo no puede distinguir entre una petición nueva y la misma petición repetida. Si tu endpoint POST siempre inserta una fila nueva, cada reintento se convierte en una segunda fila. El cliente puede ni siquiera mostrarlo, pero tu base de datos y tus clientes sí lo verán.

Los backends generados por IA son especialmente propensos a pasar esto por alto porque tienden a centrarse en la ruta feliz: un clic, una petición, una respuesta. A menudo no modelan timeouts, dobles taps ni peticiones en carrera.

El objetivo es simple: si la misma petición de creación se repite, debe producir el mismo resultado, no una segunda acción. Eso es lo que protege la idempotencia de la API.

Idempotencia de API en lenguaje llano

Idempotencia de API significa: si la misma petición se envía más de una vez, el resultado es el mismo que si se hubiera enviado una sola vez.

Esto importa porque muchos sistemas en la práctica operan con entrega "al menos una vez". Una petición puede llegar una vez o dos veces, pero normalmente llegará.

Algunas acciones son naturalmente idempotentes. Una típica petición GET puede repetirse sin cambiar nada. Actualizar una página no debería crear un usuario nuevo.

Otras acciones no son idempotentes por naturaleza. Un POST típico que crea algo (un pedido, un usuario, un cargo de pago) puede ejecutarse dos veces y producir dos registros o dos cargos. Si el primer POST tuvo éxito pero la respuesta se perdió, el cliente reintenta y crea accidentalmente un duplicado.

La idempotencia te da una forma segura de reintentar. No previene todas las fallas y no arregla lógica de negocio rota, pero sí evita acciones dobles cuando ocurren reintentos.

Dónde importa más la idempotencia

No necesitas idempotencia en todas partes. La necesitas donde un reintento puede desencadenar una segunda acción del mundo real: más dinero cobrado, más emails enviados, más trabajos iniciados, más filas creadas.

Prioriza endpoints que crean o desencadenan algo difícil de deshacer. Ejemplos comunes:

  • Pagos (charge, authorize, capture)
  • Pedidos, suscripciones, reservas
  • Envios de email/SMS (restablecimiento de contraseña, invitaciones, recibos)
  • Trabajos en segundo plano (exports, procesamiento, generación de informes)
  • Handlers de webhooks que crean o otorgan algo

También vigila endpoints que parecen "actualizaciones" pero se comportan como adiciones. Cualquier cosa que incremente contadores, añada ítems, aplique créditos o cambie totales puede aplicarse dos veces con los reintentos.

Claves de idempotencia (IDs de petición): el patrón básico

Las claves de idempotencia son la forma más práctica de hacer que las peticiones de escritura sean seguras para reintentar.

La idea es simple: el cliente envía un ID de petición único con una petición de escritura (normalmente un POST), y el servidor promete que repetir la misma clave no creará un recurso adicional ni repetirá la acción.

Un enfoque común es aceptar un header como Idempotency-Key (o un campo requestId en el body). Si el cliente se queda esperando y reintenta, reutiliza la misma clave.

En el servidor, guardas un pequeño registro por clave. La mayoría de equipos conserva:

  • la clave
  • a quién pertenece (alcance)
  • a qué operación aplica (endpoint, método)
  • estado (in_progress, succeeded, failed)
  • la respuesta que se devolvió (código de estado y body)

El alcance importa

Una clave no debería ser global en todo tu sistema. Buenos valores por defecto la limitan a uno de estos: por usuario, por cuenta/tenant, o por token de API, y normalmente por endpoint.

Por ejemplo, una clave usada para POST /orders no debería aceptarse para POST /refunds, aunque la cadena sea igual.

La retención es una compensación

Conserva las claves el tiempo suficiente para cubrir reintentos reales (minutos a horas), más un margen para clientes lentos y colas. Algunos equipos las mantienen 24 horas o unos pocos días para protegerse contra replays accidentales. Una retención más larga reduce duplicados pero aumenta el almacenamiento y te obliga a planear limpieza.

Comportamiento ante replay

Cuando se vuelve a reproducir la misma clave, el servidor debe devolver el resultado original, no ejecutar la acción de nuevo.

Ejemplo: un checkout devuelve 201 con order_id=123. Si el cliente reintenta con la misma clave, devuelve el mismo 201 y el mismo order_id.

Restricciones únicas: la red de seguridad que captura carreras

Fix One Endpoint Completely
If duplicates are already happening, we’ll fix one critical endpoint end-to-end first.

Las claves de idempotencia ayudan, pero no son suficientes por sí solas. Tu última línea de defensa es la base de datos.

Cuando dos peticiones golpean tu API casi al mismo tiempo, solo la base de datos puede impedir de forma fiable que ambas creen lo mismo.

Una restricción única le dice a la base de datos: "solo puede existir una fila así." Incluso si tu código ejecuta dos copias de la petición en paralelo, la base de datos rechazará el duplicado.

Ejemplos comunes:

  • Email único en una tabla users
  • Par único como (account_id, external_id) al importar desde Stripe, QuickBooks o un CRM
  • order_number único

Un patrón práctico es almacenar la clave de petición en el registro que creas. Por ejemplo, añade una columna idempotency_key en orders y hazla única, a menudo con alcance como (account_id, idempotency_key). Entonces cada reintento apunta a la misma fila.

Cuando se activa la restricción, tu API normalmente hace una de dos cosas:

  • Devuelve el registro existente (mejor para "crear pedido", "iniciar checkout", "crear factura")
  • Devuelve un error de conflicto claro (mejor cuando reutilizar una clave podría ocultar un error real)

No confíes en "comprueba y luego inserta" en el código de la aplicación. Dos workers pueden ambos comprobar "¿existe?" y ver "no" antes de que cualquiera inserte. Haz la unicidad una regla de la base de datos.

Cómo añadir idempotencia a un endpoint POST (paso a paso)

Si un cliente se queda esperando y reintenta un POST, quieres que la segunda llamada devuelva el mismo resultado, no cree un registro distinto.

Empieza con dos decisiones:

  • Qué acciones causan daño real si se repiten (pedidos, cargos, invitaciones, trabajos)
  • A qué está limitada la clave (por endpoint + usuario/cuenta es un buen valor por defecto)

Luego impléntalo de forma que se mantenga correcto bajo concurrencia.

1) Requiere un ID de petición estable

Para endpoints riesgosos, exige un header Idempotency-Key (o un campo de request ID). Los clientes deben reutilizar el mismo valor en reintentos.

2) Persiste la clave

O crea una tabla de idempotencia (key, endpoint, user/account, status, response) o almacena la clave directamente en el registro creado cuando hay un mapeo limpio 1:1.

3) Reclama la clave de forma atómica

Inserta primero el registro de la clave, protegido por una restricción única (o toma un lock). Así evitas que dos peticiones concurrentes "ganen".

4) Guarda la respuesta que devuelves

Tras el éxito de la acción, almacena la respuesta (código de estado y body). En repeticiones, devuelve esa respuesta almacenada sin volver a ejecutar el trabajo.

5) Decide qué hacer cuando una petición está en progreso

Si llega un reintento mientras el primer intento todavía corre, necesitas un comportamiento predecible. Opciones comunes:

  • esperar brevemente y volver a comprobar
  • devolver 409 Conflict (o 202 Accepted) con un mensaje claro de "aún procesando"

Elige un enfoque y mantenlo consistente.

Manejar casos complejos: timeouts, concurrencia y fallos parciales

Los reintentos se complican cuando el cliente y el servidor no están de acuerdo sobre lo que ocurrió. La idempotencia hace esos momentos aburridos: misma petición, mismo resultado.

Timeout después del éxito

El servidor creó el registro, pero la respuesta nunca llegó. Sin protección, el reintento crea un segundo registro.

Trata la clave de idempotencia como el recibo. Si el reintento usa la misma clave, devuelve el resultado original.

Crash a mitad de proceso y reintentos concurrentes

Almacenar una clave no basta si no registras lo que pasó.

Un conjunto práctico de reglas:

  • Guarda estado: pending, succeeded, failed.
  • Asegura que solo una petición pueda "poseer" la clave (la restricción única es la protección más simple).
  • Si un reintento encuentra pending, no inicies una segunda ejecución. Espera brevemente o devuelve una respuesta clara de "aún procesando".
  • Tras el éxito, siempre devuelve la misma respuesta para la misma clave mientras esté retenida.

Fallos parciales

Este es el caso más difícil. Puede que crees un usuario pero falles al enviar el email de bienvenida, o que captures el pago pero falles al crear la fila del pedido.

Elige una "fuente de verdad" clara para la petición y asegúrate de que los reintentos no repitan efectos secundarios que ya tuvieron éxito. A menudo eso implica:

  • completar los pasos restantes de forma asíncrona
  • usar un paso de compensación (por ejemplo, reembolsar un pago cuando no se puede crear el pedido)

Errores comunes que aún permiten duplicados

Prioritize the Right Endpoints
Not sure where to add idempotency first? We’ll help you pick the top risky flows.

La mayoría de las creaciones duplicadas no se deben a la ausencia total de idempotencia. Ocurren porque la idempotencia se añadió a medias.

Modos de fallo comunes:

  • Hacer opcional la clave de idempotencia, de modo que solo algunas peticiones estén protegidas.
  • Guardar la clave pero no el resultado, de modo que un reintento vuelva a ejecutar el trabajo.
  • No acotar las claves (una clave reutilizada entre usuarios o endpoints puede colisionar).
  • Hacer "comprueba y luego inserta" sin una restricción única en la base de datos.
  • Tratar efectos secundarios como "enviar email" como idempotentes sin registrar si se envió.

Un escenario clásico es POST /orders que primero cobra la tarjeta y luego se bloquea antes de devolver una respuesta. Si el reintento vuelve a cobrar, obtienes un doble cargo. Evítalo persistiendo el resultado y protegiéndolo con unicidad.

Lista de verificación rápida para verificar comportamiento seguro frente a reintentos

Una API segura frente a reintentos debe comportarse igual cada vez que se repite la misma acción.

Para endpoints de escritura (POST, y a veces PATCH/DELETE):

  • Exigir un Idempotency-Key para operaciones que crean o desencadenan algo.
  • Aplicar una restricción única en la base de datos para la regla de deduplicación.
  • Verificar que los reintentos devuelven el mismo ID de recurso y el mismo cuerpo de respuesta (o la misma representación estable).
  • Definir el alcance de la clave (por usuario/cuenta + por endpoint es una buena base).
  • Mantener las claves el tiempo suficiente para cubrir reintentos reales y registrar suficiente contexto para depurar.

Para handlers de webhooks:

  • Trata el ID de evento del proveedor como la clave de idempotencia, guárdalo y devuelve éxito en repeticiones.

Una prueba simple es enviar exactamente la misma petición cinco veces rápidamente (incluyendo la misma clave). Deberías ver una creación y cuatro respuestas de "mismo resultado".

Ejemplo: prevenir cargos dobles en un checkout inestable

Protect Payments and Webhooks
We’ll add safe retry behavior for payments, checkouts, webhooks, and background jobs.

Un fundador en solitario está probando un checkout que comenzó como un prototipo generado por IA. Funciona en demos, pero en uso real la red es inestable y la UI a veces se queda en un spinner.

Un cliente pulsa "Pay" una vez. No parece pasar nada. Pulsa de nuevo. Ambas peticiones llegan a la API.

Sin idempotencia, el backend trata estas como dos compras separadas. Puedes acabar con dos pagos exitosos, dos filas de pedido y un ticket de soporte que empieza con: "Solo hice clic una vez." El cliente incluso puede abrir una disputa porque no sabe cuál cargo es válido.

Con una clave de idempotencia más una restricción única en la base de datos, la segunda petición no crea nada nuevo. El servidor la reconoce como un reintento y devuelve el mismo resultado que la primera llamada: el ID de pedido original y el estado del pago.

Lo habitual es que ocurra así:

  • El cliente genera una clave de idempotencia cuando el usuario pulsa "Pay"
  • POST /checkout almacena esa clave con el intento de pedido
  • La base de datos impone unicidad en (user_id, idempotency_key) o (merchant_id, idempotency_key)
  • En el reintento, la API recupera el registro existente y lo devuelve

El soporte es más fácil si registras algunos campos: clave de idempotencia, ID de pedido resultante, ID de cargo del proveedor de pagos, marca temporal y si la petición fue un replay.

Próximos pasos para APIs generadas por IA que fallan con reintentos

Los backends generados por IA a menudo parecen bien en una demo y luego fallan cuando los usuarios refrescan, las redes móviles caen o los proveedores reintentan webhooks. Estabiliza los endpoints que pueden causar daño real si se ejecutan dos veces.

Elige tus tres operaciones más riesgosas (a menudo pagos, pedidos, invitaciones y webhooks entrantes). Añade dos capas:

  • Claves de idempotencia para hacer los reintentos seguros a propósito
  • Restricciones únicas en la base de datos para capturar carreras

Luego ejecuta una prueba pequeña y realista: doble clic en el botón de envío, simula un timeout del cliente y reintento con el mismo ID de petición, y lanza dos peticiones concurrentes con la misma carga. Quieres una única creación real y respuestas consistentes de "mismo resultado".

Si heredaste una base de código generada por IA y ya están ocurriendo duplicados, a menudo es más rápido hacer una remediación focalizada que una reescritura completa: arregla un endpoint de extremo a extremo y luego pasa al siguiente. Si quieres una segunda opinión, FixMyMess (fixmymess.ai) se especializa en diagnosticar y reparar backends generados por IA, incluyendo añadir idempotencia, salvaguardas de unicidad y comportamiento de reintento listo para producción.

Preguntas Frecuentes

¿Por qué veo registros duplicados aunque los usuarios juren que solo hicieron clic una vez?

Porque las redes y las apps reintentan. Una petición puede completarse en el servidor pero la respuesta perderse, o un usuario puede pulsar dos veces mientras la interfaz está bloqueada. Si tu POST siempre inserta una fila nueva, cada reintento puede convertirse en una segunda creación.

¿Qué significa “idempotencia de API” en términos simples?

Idempotencia significa que repetir la misma petición produce el mismo resultado que enviarla una sola vez. Para acciones de creación, normalmente implica que en el reintento se devuelve el mismo recurso en lugar de crear uno adicional.

¿Qué endpoints debería hacer idempotentes primero?

Úsala en acciones donde un reintento puede disparar un efecto real que no quieras repetir, como cobrar dinero, crear pedidos, enviar invitaciones o lanzar trabajos largos. Normalmente no la necesitas para lecturas, y puede no ser necesaria para actualizaciones inofensivas.

¿Qué es una clave de idempotencia y cómo se usa?

Una clave de idempotencia es un ID de petición generado por el cliente que se envía con una petición de escritura, a menudo en un header Idempotency-Key. El servidor registra esa clave y, si la ve de nuevo, devuelve el resultado original en lugar de volver a ejecutar la acción.

¿Cómo debo escalar las claves de idempotencia para que no colisionen?

Escópela al actor y a la operación. Un buen valor por defecto es por cuenta o usuario más el endpoint y el método, para que la misma cadena no se aplique por accidente a otro usuario o a otra acción como reembolsos.

¿Cuánto tiempo debo almacenar las claves de idempotencia?

Consérvalas el tiempo suficiente para cubrir reintentos reales y reproducciones diferidas, típicamente horas hasta un día para muchos productos. Una retención más larga reduce el riesgo de duplicados pero aumenta el almacenamiento y requiere limpieza, así que elige una ventana que puedas mantener.

¿Qué debe devolver el servidor cuando recibe la misma clave de idempotencia otra vez?

Devuelve la respuesta original para esa clave, incluyendo el mismo código de estado y cuerpo, y no repitas el efecto secundario. Esto hace que los reintentos del cliente sean seguros y previsibles, especialmente tras timeouts o conexiones perdidas.

¿Por qué sigo necesitando una restricción única en la base de datos si tengo claves de idempotencia?

Trata la restricción única de la base de datos como la guardia final contra carreras. Dos peticiones pueden pasar un “comprueba y luego inserta” al mismo tiempo, pero la base de datos puede imponer “solo una fila así”, permitiéndote recuperar y devolver el registro existente ante el conflicto.

¿Qué pasa si llega un reintento mientras la primera petición aún se está ejecutando?

Necesitas una regla clara para claves “pendientes” para no ejecutar la acción dos veces. Enfoques comunes: esperar brevemente y volver a comprobar, o devolver una respuesta clara de “aún procesando”, y después asegurar que el resultado final almacenado sea lo que recibirán los reintentos.

¿Cómo puedo probar rápidamente que mi API es segura frente a reintentos?

Empieza enviando exactamente la misma petición varias veces con la misma clave de idempotencia y confirma que obtienes una única creación y respuestas repetidas consistentes. Si heredaste un backend generado por IA que falla con reintentos, una remediación focalizada suele ser la más rápida; FixMyMess puede auditar los endpoints riesgosos y añadir idempotencia más restricciones de base de datos de extremo a extremo.