Idempotencia
La idempotencia es la garantía de que ejecutar la misma operación dos veces produce exactamente el mismo resultado que ejecutarla una sola vez. Cuando mueves dinero, esta garantía deja de ser un lujo y se vuelve el cimiento de todo: es lo que te permite reintentar un request sin miedo cuando la red falla, cuando tu servidor se cae a mitad de camino o cuando no sabes si tu pago llegó. En Alibanca la controlas con un solo header, idempotency-key, y en esta página te explicamos qué es, por qué importa tanto, cómo generar buenas claves y cómo se relaciona con los estados failed y unknown de un pago.
¿Qué es la idempotencia? (en simple)
Imagina el botón de un ascensor. Lo presionas una vez y el ascensor viene. Si lo presionas cinco veces más porque estás apurado, el ascensor no viene cinco veces ni sube cinco pisos: el resultado es el mismo que si lo hubieras presionado una sola vez. Eso es idempotencia. Una operación es idempotente cuando repetirla no cambia el resultado más allá de la primera vez.
En una API que mueve dinero esto es justo lo contrario de lo que pasaría con un botón "peligroso". Piensa en un cajero que dispensa efectivo cada vez que lo tocas: presionarlo cinco veces te da cinco veces el dinero. Ese comportamiento, aplicado a pagos, es una catástrofe: cada reintento sería un pago nuevo. La idempotencia convierte tu endpoint de pagos en un botón de ascensor y no en un cajero descontrolado.
GET es naturalmente idempotente (leer un saldo diez veces no cambia nada). Un POST que crea un pago no lo es por naturaleza — por eso Alibanca se lo agrega con la idempotency-key.Por qué es crítica al mover dinero
El problema que resuelve la idempotencia se llama incertidumbre de red. Cuando tu servidor le manda un POST /v2/payments a Alibanca, pueden pasar tres cosas: (1) el request llega, se procesa y recibes la respuesta — todo bien; (2) el request nunca llega — nada se movió; o (3), la más traicionera, el request llega y se procesa, pero la respuesta se pierde en el camino de vuelta. En este tercer caso tu código no recibió nada: no sabe si el pago ocurrió o no.
Sin idempotencia, tu única salida ante ese silencio sería "reintentar por si acaso". Y ese reintento crearía un segundo pago por el mismo dinero. Multiplicado por miles de operaciones y timeouts inevitables, terminas dispersando el doble. Con idempotencia, reintentar es seguro: le mandas a Alibanca la misma idempotency-key del intento anterior y el sistema reconoce "esta operación lógica ya la vi" y te devuelve el resultado original en vez de mover dinero otra vez.
El header idempotency-key
La idempotencia en Alibanca se activa con un header en tu request:
| Header | Tipo | Descripción |
|---|---|---|
| idempotency-keyreq en pagos | string | Identificador único y determinístico de la operación lógica que estás ejecutando. "Único" = no lo reutilizas para dos operaciones distintas. "Determinístico" = tu código puede volver a calcular exactamente la misma clave para el mismo pago, aunque se reinicie. Es tu manera de decirle a Alibanca "estas dos peticiones son el mismo pago, no dos pagos". |
Un detalle importante de seguridad: la idempotency-key no viaja sola. Además de ser un header, forma parte del envelope canónico que firmas con tu llave privada (RSA-SHA256). Ese envelope es la concatenación determinística de [método HTTP, ruta, api-key, idempotency-key, cuerpo crudo, nonce], en ese orden. O sea que la clave está firmada junto con el cuerpo del request: nadie puede alterarla en tránsito sin romper la firma. Si te interesa el detalle de la firma, revísalo en Autenticación y firma →; para lo de idempotencia te basta con saber que la clave es un header que además queda protegido por la firma.
idempotency-key con el nonce. Son cosas distintas. El nonce es un número siempre creciente (high-water) que evita replays — impide que alguien reenvíe un request viejo capturado. La idempotency-key hace lo opuesto: existe precisamente para que tú puedas repetir la misma operación a salvo. Uno bloquea reenvíos maliciosos; el otro habilita reintentos tuyos.Cómo generar una clave: única y determinística
La palabra clave es determinística. Mucha gente resuelve la unicidad con un UUID aleatorio nuevo en cada llamada — y eso es justo lo que no quieres, porque si tu servidor se cae y reintenta, generaría un UUID distinto y crearía un pago nuevo. La clave debe poder recalcularse igual a partir de datos estables de tu operación.
Ánclala a la operación de negocio, no al request
Piensa en cuál es la "operación lógica" que jamás debe ocurrir dos veces. Por ejemplo: "el payout #123 de la orden de retiro del usuario X". Esa unidad de negocio tiene un identificador estable en tu base de datos. Úsalo como semilla de la clave.
Hazla reproducible tras un reinicio
Si tu proceso muere y se levanta de nuevo, tu código tiene que llegar a la misma clave leyendo el mismo registro. Por eso conviene persistir la clave en tu DB junto al payout antes de mandar el request, o derivarla de campos inmutables (id del payout, no la hora actual).
Hazla única entre operaciones distintas
Dos payouts diferentes nunca deben compartir clave. Un prefijo con el tipo de operación más el id interno funciona muy bien: payout-2026-000123. Si dudas, agrégale un componente que garantice unicidad de negocio (id de la orden + secuencia).
Mantenla acotada y estable
Un string corto, ASCII, estable en el tiempo. No metas dentro de la clave datos que puedan cambiar entre intentos (timestamps, contadores de reintento, hostname). Si cambian, dejaría de ser la misma clave y perderías la protección.
{tipo}-{id-de-negocio}, por ejemplo payout-{orderId} o disbursement-{ledgerEntryId}. Si necesitas más entropía, usa un hash determinístico de esos campos (no un aleatorio). La prueba de fuego: "si mi servidor se reinicia justo ahora, ¿mi código genera exactamente esta misma clave?" Si la respuesta es no, la clave está mal.Qué pasa cuando reintentas
Aquí está el corazón del mecanismo, y depende por completo de qué clave uses al reintentar.
Reintento con la MISMA idempotency-key
Alibanca reconoce que esa operación lógica ya la procesó. No crea un pago nuevo. Te devuelve el mismo resultado que la primera vez — el mismo id de pago, el mismo state, el mismo monto. Da igual si reintentas una vez o veinte: siempre obtienes el pago original. Esto es lo que hace seguro reintentar tras un timeout: en el peor caso, "molestaste" a la API para que te repita algo que ya existía.
Un pago genuinamente nuevo necesita una clave NUEVA
La otra cara: si de verdad quieres emitir otro pago (por ejemplo, un segundo retiro real del mismo usuario), tienes que usar una idempotency-key diferente. Si reutilizas por error la clave de un pago anterior para una operación distinta, Alibanca te devolverá el pago viejo y tu nuevo pago no ocurrirá. Por eso "único entre operaciones distintas" es tan importante como "determinístico dentro de la misma operación".
Idempotencia + estados del pago: la regla de oro
La idempotencia no vive sola: se combina con los estados de un pago para decirte cuándo reintentar y con qué clave. Un pago en Alibanca termina en uno de tres estados:
Cada uno cambia tu jugada de idempotencia:
| Estado | Qué significa | Qué haces |
|---|---|---|
| completed | éxito | El dinero se movió. No reintentas nada. Si por un timeout no viste esta respuesta, un reintento con la misma clave te la devolverá. |
| failed | fallo garantizado | Garantía dura: NO hubo débito. El dinero no se movió, punto. Es seguro emitir el pago de nuevo — pero como es un intento genuinamente nuevo, usa una idempotency-key NUEVA. |
| unknown | indeterminado | El banco no respondió (timeout/caída). "No sabemos todavía." NUNCA creas un pago nuevo sobre un unknown. Se resuelve solo por conciliación de saldo. Reintentar aquí es exactamente lo que produce el doble-pago. |
failed = seguro reintentar, con clave nueva (garantiza que no hubo débito). unknown = no reintentar (se reconcilia). El estado unknown es de primera clase en Alibanca precisamente porque el mundo real de la banca tiene timeouts, y tratar "no sé" como "falló" es lo que rompe la integridad del dinero.Fíjate en la sutileza: la idempotency-key te protege del doble-pago cuando reintentas la misma operación (misma clave). Pero si un pago quedó en unknown y tú decides "voy a hacer otro con clave nueva", la idempotencia ya no te cubre — porque le estás diciendo explícitamente a Alibanca que es un pago distinto. Por eso la disciplina de estados manda: sobre un unknown, esperas la conciliación; no fabricas una operación nueva.
Ejemplo completo: un pago y su reintento
Veamos el flujo real. Primero emites un pago con su idempotency-key determinística derivada de tu payout interno #123:
curl -X POST https://alibanca-api-production.up.railway.app/v2/payments \ -H "x-api-key: sk_test_TU_LLAVE" \ -H "x-signature: <firma RSA-SHA256 del envelope>" \ -H "idempotency-key: payout-2026-000123" \ -H "Content-Type: application/json" \ -d '{"amountMinor":"150000","currency":"VES","beneficiaryId":"ben_a1b2c3"}'
El mismo request desde Node, generando la clave a partir de un id de negocio estable (no un aleatorio):
// La clave se deriva del payout interno: reproducible tras un reinicio const idempotencyKey = `payout-2026-${payout.id}`; const res = await fetch("https://alibanca-api-production.up.railway.app/v2/payments", { method: "POST", headers: { "x-api-key": process.env.ALIBANCA_KEY, "x-signature": sign(envelope), "idempotency-key": idempotencyKey, "content-type": "application/json" }, body: JSON.stringify({ amountMinor: "150000", currency: "VES", beneficiaryId: "ben_a1b2c3" }) });
La respuesta trae el pago creado, con su id y su state. Envuelta en el sobre estándar de Alibanca:
{ "status": "success", "traceId": "trc_9f2c7a10", "payload": { "id": "pay_7h3k9m", "state": "completed", "subStatus": "settled", "amountMinor": "150000", "currency": "VES" } }
Ahora supón que tu servidor se cayó justo después de enviar el request y nunca vio esta respuesta. Al levantarse, recalcula la misma clave (payout-2026-000123) porque la derivó del mismo payout, y reenvía exactamente el mismo request. Alibanca reconoce la operación y te devuelve el pago original — el mismo id, sin mover dinero de nuevo:
{ "status": "success", "traceId": "trc_5b81ce44", "payload": { "id": "pay_7h3k9m", // mismo id: NO es un pago nuevo "state": "completed", "subStatus": "settled", "amountMinor": "150000", "currency": "VES" } }
idempotency-key tuya y luego reenvía el mismo request con la misma clave. Confirma que el id del payload es idéntico en ambas respuestas: esa es tu prueba de que el reintento fue idempotente y no creó un segundo pago. Los montos mágicos → te dejan además ensayar el caso unknown del doble-pago sin arriesgar dinero.Buenas prácticas
Persiste la clave antes de enviar
Guarda la idempotency-key en tu base de datos, junto al payout, antes de disparar el request. Así, si algo falla en medio, tu proceso de recuperación lee esa misma clave y reintenta sin ambigüedad. La clave debe existir en tu lado con la misma vida útil que la operación de negocio.
Deriva, no inventes
Prefiere derivar la clave de identificadores estables (id del payout, id de la orden) en vez de generarla al azar. Si usas un aleatorio, al menos guárdalo persistido de inmediato para poder reusarlo. La palabra que nunca falla es determinística.
Un reintento no cambia el cuerpo
Cuando reintentas con la misma clave, manda el mismo cuerpo (mismo amountMinor, mismo destino). La idempotency-key identifica una operación concreta; cambiarle el monto a mitad de camino es una contradicción lógica. Recuerda además que el cuerpo crudo va firmado en el envelope, así que un reintento consistente también es un reintento verificable.
Combina reintento con consulta de estado
Ante un timeout tienes dos herramientas complementarias: reintentar el POST con la misma clave, o consultar GET /v2/payments/:id → para leer el state actual. Ambas son seguras porque ninguna crea un pago. Usa la que encaje mejor en tu flujo — muchas integraciones reintentan un par de veces y, si siguen sin certeza, pasan a consultar estado.
Respeta la Regla de Oro por estado
Automatiza la decisión: si el pago quedó failed, tu sistema puede reemitir con clave nueva; si quedó unknown, tu sistema marca y espera la conciliación, sin tocar nada. No dejes esa decisión al azar de un reintento ciego.
Errores comunes que debes evitar
unknown. "El pago quedó en unknown, lo mando otra vez con clave nueva por si acaso." Ese "por si acaso" es la trampa: si el dinero sí se había movido, acabas de pagar doble. Sobre unknown nunca se crea un pago nuevo — se reconcilia.En resumen
La idempotencia convierte tus reintentos en una operación segura: repetir un pago con la misma idempotency-key devuelve el pago original en vez de mover dinero otra vez, y emitir un pago genuinamente nuevo exige una clave nueva. Genera claves únicas y determinísticas ancladas a tu operación de negocio, persístelas antes de enviar, y combina el mecanismo con la Regla de Oro de los estados: failed se reintenta con clave nueva porque garantiza que no hubo débito, y unknown nunca se reintenta porque se resuelve por conciliación. Con esa disciplina, mover dinero se vuelve una operación repetible sin riesgo — que es exactamente lo que la infraestructura de pagos de Alibanca está diseñada para darte. Sigue con Estados de un pago → y Montos mágicos → para ensayarlo todo en sandbox.