Un timeout no indica que una operación haya fallado: sólo confirma que el cliente no recibió una respuesta dentro del plazo. El servidor puede haber creado el pedido, el proveedor de pagos puede haber aceptado el cobro o un proceso asíncrono puede seguir ejecutándose. Si el cliente reintenta sin control, una misma intención de negocio puede producir efectos duplicados.
La idempotencia en PHP convierte una repetición técnica en una consulta o en la devolución del resultado ya obtenido. No consiste en ignorar todos los duplicados ni en confiar únicamente en que el usuario no pulse dos veces. Es un contrato explícito entre cliente, API, persistencia y, cuando aplica, sistemas externos.
El problema: la respuesta se pierde, pero el efecto permanece

Considere un endpoint que confirma una compra. La aplicación valida la solicitud, registra el pedido, solicita el cobro y prepara una respuesta. La conexión se corta justo antes de que el cliente la reciba. Al reenviar el mismo formulario, el endpoint no puede deducir por el contenido que se trata de la misma compra: dos pedidos con los mismos productos pueden ser intenciones válidas y distintas.
El problema aparece también en altas de usuarios, asignación de créditos, emisión de documentos, sincronizaciones, webhooks y acciones administrativas. Hay tres elementos que conviene separar:
- Intención de negocio: «quiero confirmar esta compra concreta».
- Petición técnica: un envío HTTP con cabeceras, cuerpo y contexto de autenticación.
- Intento de ejecución: cada procesamiento interno, reintento de cola o llamada a un proveedor.
La clave idempotente identifica la intención, no una conexión HTTP ni cada intento del servidor. Por eso debe sobrevivir a reintentos de red y, cuando el flujo lo requiere, a reinicios del proceso.
Qué operaciones necesitan idempotencia y cuáles no
Priorice operaciones que crean, confirman, cobran, envían, reservan, notifican o modifican un recurso con consecuencias relevantes. Un POST /payments, la confirmación de un pedido o la recepción de un webhook son candidatos claros. También lo es un trabajo de cola que puede entregarse más de una vez.
Una lectura pura normalmente no necesita una clave de idempotencia. Una actualización puede tener una semántica distinta: establecer un estado deseado, como PUT /profiles/42, puede ser idempotente por diseño si la misma representación deja el recurso igual. En cambio, una acción como «sumar saldo» no lo es sólo por usar un verbo determinado.
Tampoco debe usarse una clave como sustituto de otras reglas. Para impedir dos reservas compatibles en un inventario limitado se necesitan invariantes de dominio, control de concurrencia y una política de reserva. Para ejecutar una tarea una sola vez en un entorno distribuido, la entrega real suele ser al menos una vez; el consumidor debe tolerar duplicados.
Diseño de la clave y del registro persistente
El cliente debería generar una clave opaca y suficientemente impredecible cuando nace la intención de negocio, conservarla mientras pueda reintentar y enviarla, por ejemplo, en Idempotency-Key. Si el servidor la genera en cada recepción, no podrá vincular una repetición posterior. En flujos internos, la clave puede derivarse de un identificador estable del evento de negocio.
Su ámbito debe incluir el actor o tenant y la operación. La misma cadena no debería colisionar entre dos cuentas ni entre «crear pedido» y «emitir reembolso». Defina una retención alineada con el periodo real de reintentos y con los riesgos del dominio. Eliminar el registro demasiado pronto vuelve a abrir la puerta al duplicado; conservarlo indefinidamente aumenta coste y exige una política de privacidad y borrado.
Un modelo mínimo de persistencia incluye:
- ámbito de seguridad o tenant, nombre de operación y clave idempotente;
- huella criptográfica de una carga útil normalizada;
- estado:
processing,completed,failedopendingcuando la confirmación externa es incierta; - código y cuerpo de respuesta que se devolverán de forma repetible;
- identificadores del recurso creado, correlación interna y referencia del proveedor externo;
- fechas de creación, actualización y expiración.
La huella evita un error importante: reutilizar la misma clave con datos distintos. Ante esa situación, responda con un conflicto y no procese la nueva carga. Para que la comparación sea fiable, normalice campos cuyo orden no tenga significado y excluya metadatos cambiantes que no formen parte de la intención.
Flujo PHP: reservar antes de producir el efecto
La protección debe estar respaldada por una restricción única en base de datos sobre el ámbito, la operación y la clave. Consultar primero y después insertar no basta: dos solicitudes simultáneas pueden observar la ausencia del registro y continuar a la vez.
El flujo recomendado es reservar de manera atómica. Si la inserción tiene éxito, ese proceso es propietario inicial de la ejecución. Si hay conflicto de unicidad, se lee el registro existente, se verifica la huella y se actúa según su estado. Un resultado completado devuelve exactamente la respuesta persistida; una operación en curso puede devolver un estado pendiente o esperar sólo un intervalo acotado antes de consultar de nuevo.
begin transaction
insert idempotency_records(scope, operation, key, payload_hash, status)
values (?, 'create_order', ?, ?, 'processing')
-- la restricción única decide el propietario
commit
if reservation_was_created:
result = execute_business_operation()
persist_completed_response(result)
else:
record = load_existing_record()
assert_same_payload_hash(record)
return replay_or_pending(record)No mantenga una transacción ni un bloqueo de fila abiertos durante una llamada lenta a un proveedor. Eso reduce capacidad y puede generar bloqueos prolongados. En su lugar, reserve y confirme el estado local en transacciones breves. Si el efecto externo y el registro local deben coordinarse, almacene además una orden de envío en una tabla transaccional y procésela de forma separada. Este patrón no elimina los reintentos, pero permite recuperar el trabajo pendiente sin perder la intención registrada.
Concurrencia, timeouts y estados inciertos
Dos solicitudes con la misma clave pueden llegar con milisegundos de diferencia. La restricción única establece cuál reserva la operación. La segunda no debe iniciar otro efecto externo. Puede responder 202 mientras el estado sea processing o pending, incluyendo un identificador para consultar el resultado; si el contrato exige una respuesta síncrona, puede hacer una espera limitada y volver a leer el registro.
Un fallo antes de iniciar cualquier efecto permite marcar failed con un error reproducible. Sin embargo, un timeout al llamar a un sistema externo crea incertidumbre: no es correcto marcar automáticamente como fallido ni reenviar una orden sin más. Guarde la referencia de solicitud enviada si existe, consulte al proveedor mediante esa referencia y concilie el resultado. Mientras no haya confirmación, mantenga pending y comunique que el resultado todavía no es definitivo.
La llamada externa también necesita una referencia estable. Si el proveedor admite su propia clave idempotente, propague una clave asociada a la misma intención. Si no la admite, use identificadores de comercio, lectura posterior, conciliación periódica y procedimientos operativos para los casos ambiguos. Ninguna transacción local puede volver atómica una escritura en la base de datos y una API remota independiente.
Lo que una clave de idempotencia no resuelve
La idempotencia evita repetir una intención reconocida; no decide cómo deshacer un efecto irreversible. Un envío físico, una transferencia ya liquidada o una notificación vista por un usuario pueden requerir compensación, cancelación o atención manual. Diseñe esas acciones como procesos de negocio explícitos, con permisos, estados y auditoría.
Tampoco confunda una corrección con un reintento. Si el usuario cambia dirección, importe o productos después de un error, hay una nueva intención y debe usar una nueva clave. Reutilizar la anterior con otra carga debe dar conflicto, no actualizar silenciosamente la operación original.
Pruebas, observabilidad y lista de comprobación

Pruebe más que el camino feliz. Interrumpa la respuesta después de persistir el resultado, repita la misma clave en paralelo, reinicie un trabajador tras reservar el registro y simule un timeout después de enviar una petición externa. Verifique que sólo existe un recurso de negocio, que la respuesta repetida conserva el mismo resultado y que la carga distinta con la misma clave no se acepta.
Registre, sin exponer datos sensibles, la clave o un identificador seguro derivado, el ámbito, el estado, la correlación y la referencia externa. Las métricas de conflictos de clave, operaciones pendientes durante demasiado tiempo y conciliaciones sin resolver ayudan a soporte y operaciones a distinguir un reintento normal de una incidencia.
- ¿La clave representa una intención de negocio y tiene un ámbito definido?
- ¿Existe una restricción única que impida dos reservas concurrentes?
- ¿Se compara una huella de la carga y se rechazan cambios de intención?
- ¿Se persiste una respuesta o resultado que pueda repetirse de forma coherente?
- ¿Los estados inciertos permiten consultar y conciliar antes de reintentar?
- ¿Cada efecto externo dispone de referencia, recuperación y alternativa operativa?
- ¿Se han probado duplicados, caídas, reintentos de cola y concurrencia real?
Aplicada así, la idempotencia no promete que una red sea fiable. Hace que los fallos inevitables tengan un resultado controlable, trazable y coherente para el negocio.



