Definición
La idempotency key (clave de idempotencia) es un identificador único que un cliente envía con una petición mutante (normalmente un POST) para garantizar que, ante un reintento de red, la operación se ejecute una sola vez.
El servidor almacena la clave; si vuelve la misma petición, devuelve la respuesta anterior sin reejecutar la operación. Es una best practice crítica para cualquier API de pago, donde una doble ejecución significa pago doble, facturación doble o estado corrupto. Stripe, Bridge, Adyen, Mollie, AWS y muchos otros la exigen o la admiten.
Por qué es crítica
En la red, la incertidumbre es la norma: una petición puede expirar sin que se sepa si fue recibida, procesada o si un reintento duplicará la operación. Sin clave de idempotencia es imposible reintentar con seguridad; con ella, el cliente puede reintentar con tranquilidad, y el servidor devuelve la respuesta almacenada si la operación ya tuvo lugar.
Cómo funciona
Formato y buenas prácticas
- Generación: UUID v4 (aleatorio), ULID (ordenado en el tiempo, debug-friendly) o hash de negocio determinista (
SHA256(user_id + transaction_ref)). - Header:
Idempotency-Key: 4a7f3b80-fe5c-4d12-9c7e-83b3a4ab9b1c. La IETF publicó un borrador, pero la convención se adoptó mucho antes de su oficialización. - Duración de caché: 24h en Stripe y Bridge, 1h en Mollie. Recomendado: 24 a 48h para pagos, 7 a 30 días para los flujos asíncronos. Más allá, un reintento reejecutaría la operación.
- Ámbito: por comercio / por cuenta (recomendado), en lugar de global (riesgo de colisión).
Payload distinto, misma clave
Si la misma clave se envía con un payload distinto, el servidor debería rechazar la petición (409 Conflict), como hacen Stripe y AWS. Esto evita que un payment 200 € se ignore silenciosamente tras un payment 100 € con la misma clave.
Casos de uso
- Pagos:
POST /payments,/transfers,/charges,/refunds— siempre. - Creación de recursos:
POST /customers,/subscriptions— para evitar duplicados. - Operaciones costosas:
POST /kyc,/loans/disburse. - Innecesaria en:
GET(idempotente por naturaleza),PUTcon ID explícito,DELETE.
Idempotency-Key vs otras técnicas
| Técnica | Garantía | Complejidad |
|---|---|---|
| Idempotency-Key (header) | Exactly-once por llamada | Baja del lado del cliente |
| Token de transacción único | Exactly-once por transacción de negocio | Media |
| Bloqueo optimista de BD | Exactly-once por recurso | Media |
| Saga + commands | Exactly-once por flujo | Alta |
La Idempotency-Key actúa a nivel de API: no protege frente a un bug de negocio (un usuario que paga dos veces mediante dos botones distintos genera dos claves diferentes).
Implementación del lado del servidor
Almacenamiento típico: Redis (TTL nativo, clave idempotency:{merchant}:{key}), DynamoDB para la escalabilidad o PostgreSQL en volumen bajo.
def handle_payment(req):
key = req.headers.get("Idempotency-Key")
if not key:
return create_payment(req)
cached = redis.get(f"idempotency:{merchant}:{key}")
if cached:
return cached
with redis.lock(f"idempotency-lock:{merchant}:{key}", ttl=30):
cached = redis.get(f"idempotency:{merchant}:{key}")
if cached:
return cached
response = create_payment(req)
redis.setex(f"idempotency:{merchant}:{key}", 86400, response)
return responseEl bloqueo es necesario si dos peticiones con la misma clave llegan a la vez (race condition de reintento), para evitar una doble ejecución en paralelo.
Comportamiento ante un fallo
- Timeout del cliente, éxito en el servidor: reintento con la misma clave → respuesta en caché (éxito).
- Error 5xx: la mayoría de los proveedores reejecutan (un 5xx no se considera definitivo).
- Error 4xx: reintento con la misma clave → se devuelve la respuesta 4xx en caché.
Lo que la Idempotency-Key no es
- No es una transacción distribuida: no garantiza la coherencia entre servicios.
- No es una protección de negocio: dos clics en dos botones generan dos claves.
- No es un reemplazo de la autenticación: es complementaria.
- No es necesaria en GET / PUT: idempotentes de forma nativa.
- No es (todavía) un estándar formal: borrador IETF, pero una convención casi universal.
En el ecosistema PSD2 / Open Finance
- Berlin Group: recomienda
X-Request-ID(equivalente). - STET: header
X-Request-ID. - OBIE UK:
x-idempotency-key. - PSP adquirentes (Stripe, Adyen, Mollie): adoptada universalmente.
- Webhooks: la clave también es crucial en la recepción, para no procesar dos veces un mismo evento.
Ejemplos concretos
- Stripe: header
Idempotency-Key, caché de 24h, rechazo de payload distinto — el estándar del sector. - Bridge:
Idempotency-Key+X-Request-IDsegún el endpoint. - Mollie:
Idempotency-Key, caché de 1h. - AWS: parámetro
client-token. - Caso real: un comercio sin clave de idempotencia reintenta tras un timeout → doble adeudo en el 0,5 % de los pagos → decenas de miles de euros en reembolsos y pérdida de confianza.
- Patrón de UI: generar la clave en el momento del clic y persistirla en el state del formulario — un reclic 5 segundos después reutiliza la misma clave, sin doble cargo.