Idempotencia
Cómo reintentar sin registrar la misma venta dos veces.
El problema
Tu bot manda un pago. La red se corta después de que lo procesamos pero antes de que te llegue la respuesta. Tu bot reintenta. Sin protección, el negocio termina con la misma venta dos veces y culpando a Verifika.
La solución
Manda una cabecera Idempotency-Key única por operación en cada escritura (POST, PATCH,
DELETE) — sobre todo en POST /v1/verifications,
donde un reintento sin ella puede registrar el mismo comprobante dos veces si no trae referencia:
curl -X POST https://api.verifika.tech/v1/incomes \
-H "Authorization: Bearer vk_live_..." \
-H "Idempotency-Key: 8f14e45f-ea28-4b4c-9f2a-0b1d3c5e7a90" \
-H "Content-Type: application/json" \
-d '{ "amount": 45000, "reference": "M1A2B3C4" }'Si repites la petición con la misma llave y el mismo cuerpo, no ejecutamos nada: te
devolvemos la respuesta original, con la cabecera Idempotent-Replay: true.
| Situación | Qué pasa |
|---|---|
| Llave nueva | Se ejecuta y se guarda la respuesta |
| Llave repetida, mismo cuerpo, ya respondida | Respuesta original + Idempotent-Replay: true |
| Llave repetida, mismo cuerpo, todavía ejecutando | 409 IDEMPOTENCY_IN_PROGRESS — reintenta en unos segundos |
| Llave repetida, cuerpo distinto | 409 IDEMPOTENCY_KEY_REUSED |
Cómo generar la llave
Un UUID v4 por operación de negocio, no por intento HTTP. Es decir: se genera una vez, antes del primer envío, y se reusa en todos los reintentos de esa misma operación.
const idempotencyKey = crypto.randomUUID(); // una vez, fuera del bucle de reintentos
for (let intento = 1; intento <= 3; intento++) {
const res = await fetch('https://api.verifika.tech/v1/incomes', {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify(pago),
});
if (res.ok) return res.json();
if (res.status < 500 && res.status !== 429) throw new Error(await res.text());
await new Promise((r) => setTimeout(r, 2 ** intento * 500));
}Un error no se guarda
Solo cacheamos respuestas exitosas. Si te devolvimos un 400, puedes corregir el cuerpo y
reintentar con la misma llave: la reserva se libera. Si guardáramos los errores, un
problema pasajero se volvería permanente.
En una verificación, un rechazo no es un error
POST /v1/verifications responde 200 con status: DUPLICATE o REJECTED cuando el comprobante
está repetido o no es un comprobante. Es una respuesta exitosa, así que se guarda: repetir con
la misma llave devuelve el mismo veredicto. En cambio, un 503 AI_UNAVAILABLE no se guarda: puedes
reintentarlo con la misma llave.
Las llaves se recuerdan 24 horas. Después de eso, la misma llave se trata como nueva.
Dos detalles más: la llave se cuenta por API key (dos llaves distintas pueden usar el mismo
valor sin pisarse) y admite hasta 255 caracteres; con más, 400.