VerifikaDocs
Guías

Idempotencia

Cómo reintentar sin registrar la misma venta dos veces.

Aplica a las escrituras, que llegan en la fase 1

La cabecera ya está implementada y aceptada en /v1. Los endpoints de escritura (POST /v1/incomes, POST /v1/expenses, …) se publican en la siguiente fase; esta página existe para que la construyas bien desde el primer día.

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 POST:

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ónQué pasa
Llave nuevaSe ejecuta y se guarda la respuesta
Llave repetida, mismo cuerpo, ya respondidaRespuesta original + Idempotent-Replay: true
Llave repetida, mismo cuerpo, todavía ejecutando409 IDEMPOTENCY_IN_PROGRESS — reintenta en unos segundos
Llave repetida, cuerpo distinto409 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.

Las llaves se recuerdan 24 horas. Después de eso, la misma llave se trata como nueva.

En esta página