Skip to content

Idempotencia ​

En toda operación que crea o muta datos (POST, PATCH, PUT) puedes mandar el header Idempotency-Key. Si por cualquier motivo (red intermitente, doble-click, reintento de un job) el mismo request llega dos veces, te devolvemos la respuesta de la primera vez en lugar de duplicar.

Cómo usarla ​

Genera un identificador único por intento conceptual de la operación. Mándalo en el header:

Idempotency-Key: f1a2b3c4-d5e6-7890-abcd-ef0123456789

Recomendación: usa un UUID v4 o un hash del payload + timestamp.

Reglas ​

  1. La key vive 24 horas desde el primer uso. Después de eso, la cache se libera y la misma key puede reusarse para un request distinto.
  2. La cache está scopada por empresa: la misma key entre dos cuentas distintas no colisiona.
  3. Si reusas una key con un body idéntico, te devolvemos la respuesta cacheada y agregamos el header Idempotent-Replayed: true.
  4. Si reusas una key con un body distinto, recibes 422 idempotency_key_reused. Cambia la key o iguala el body.
  5. Solo cacheamos respuestas con status 2xx. Si el primer intento dio 422 o 500, no hay caché y un retry pasa por el flujo normal.
  6. El body máximo idempotency-keyed es 100 KB. Bodies más grandes recibirán 413 idempotency_body_too_large y debes quitar el header.

Ejemplo ​

javascript
const idempotencyKey = crypto.randomUUID()

async function createLead(payload) {
  return fetch('https://developers.fi-nova.com/api/v1/leads', {
    method: 'POST',
    headers: {
      'Authorization':   `Bearer ${SECRET}`,
      'Content-Type':    'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify({ data: payload }),
  })
}

// Primer intento (red flaky) — falla en la red, pero el server ya creó el lead.
await createLead({ name: 'Laura' }).catch(() => null)

// Reintento. Misma key, mismo body. NO duplicamos — devolvemos la respuesta original.
const res = await createLead({ name: 'Laura' })
res.headers.get('Idempotent-Replayed') // → 'true'

Por qué no idempotencia automática ​

Podríamos hashear el body y deduplicar implícitamente, pero eso sería frágil — dos requests con el mismo contenido pero distinto intent conceptual (ej. dos formularios enviados por personas distintas con coincidentemente el mismo email) se colapsarían a uno. Pedir explícitamente la key te deja el control.

Mejores prácticas ​

  • Genera la key del lado del cliente, no del servidor que despacha. Si tu frontend hace doble-click, ambas requests llevan la misma key — perfecto. Si tu lambda regenera la key en cada invocación, perdiste la idempotencia.
  • Persiste la key con el form data mientras el usuario llena el formulario. Así, si refresca, la próxima envío con el mismo contenido sigue siendo el mismo request.
  • No reuses keys entre operaciones distintas. create-lead-abc no debe usarse luego para create-customer-xyz.

¿GETs? ​

Los GET son ya idempotentes por definición HTTP — no necesitas el header. Lo ignoramos si lo mandas.


Idempotency-Key no basta para el dinero ​

La cabecera guarda la respuesta original 24 horas. Eso cubre el reintento inmediato: el doble clic, la red que se cae a mitad, el reintento automático de tu cliente HTTP.

No cubre el otro caso, que es el que duele:

El POST llegó y creó la venta. La respuesta se perdió por el camino. Tu proceso murió antes de anotar nada. Tres horas después, un barrido de reconciliación ve una orden sin venta en Finova y la vuelve a registrar.

Ahí la clave ya caducó, o el nuevo proceso genera una distinta, y el resultado es un movimiento contable duplicado: dinero contado dos veces, sin forma de distinguir la copia del original.

metadata.external_ref ​

Para eso está. En los recursos que mueven dinero — ingresos, ventas y recurrencias — mandas tu propia referencia dentro de metadata:

json
{ "data": { "…": "…", "metadata": { "external_ref": "ord_123" } } }

Lleva un índice único por empresa. Si repites el alta con la misma referencia, recibes 200 con el recurso que ya existía en lugar de un segundo.

Idempotency-Keymetadata.external_ref
Dónde vacabeceracuerpo, dentro de metadata
Cuánto dura24 hpara siempre
Qué comparael cuerpo enterosólo la referencia
Dónde se decidecaché compartidaíndice único en la base
Aplica acualquier POST/PUTingresos, ventas, recurrencias

El 200 en vez de 201 es la señal de «ya estaba». No es un error: para ti, «lo acabo de crear» y «ya existía» son el mismo desenlace bueno, y devolverte un 409 sólo te obligaría a escribir el camino de recuperación que este campo ya es.

Úsalos juntos

Idempotency-Key para el reintento inmediato, external_ref para el que llega tarde. No se estorban.

Elige bien la referencia ​

Tiene que ser estable y derivable de tus datos, no aleatoria: el reintento tiene que poder calcular la misma. Un SecureRandom.uuid nuevo en cada intento no protege de nada.

BienMal
ord_123 (tu id de orden)uuid generado en cada intento
sub_abc:2026-10 (tu suscripción + periodo)la marca de tiempo del intento

Hecho con cuidado por Finova.