Idempotencia
Concepto
Si tu sistema reintenta un POST /facturas por un timeout de red, sin idempotencia podrías generar dos comprobantes para la misma operación. Eso es un desastre fiscal.
La idempotencia garantiza que un POST con la misma Idempotency-Key produce el mismo resultado, sin importar cuántas veces se reintente.
Uso
Enviá un UUID único por operación de negocio en el header:
Idempotency-Key: 4f1c8a8a-2c4f-4b5e-9c2a-1234567890abComportamiento
| Escenario | Respuesta |
|---|---|
| Primera vez con esa key | Procesa normal. Cachea respuesta 24 h. |
| Reintento con misma key + mismo body | Devuelve la respuesta cacheada (mismo status + body). No se re-emite. |
| Reintento con misma key + body distinto | 409 Conflict |
| Pasadas 24 h | Cache expira. Un POST con esa key vuelve a procesarse normal. |
Reglas
✅ Generá la key de tu lado, ANTES del primer intento. ✅ Usá la misma key en todos los reintentos del mismo intento de negocio. ✅ Una key = una operación de negocio. No la reutilicés entre comprobantes distintos. ✅ Largo máximo: 100 caracteres. ✅ Formato recomendado: UUID v4.
Patrón correcto
async function emitir(facturaData, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID(); // ← una vez, fuera del loop
for (let intento = 0; intento < maxRetries; intento++) {
try {
return await axios.post('/api/v1/facturas', facturaData, {
headers: {
'X-Api-Key': process.env.LXS_API_KEY,
'Idempotency-Key': idempotencyKey // ← mismo valor en cada reintento
},
timeout: 30000
});
} catch (err) {
if (err.response?.status >= 500 || err.code === 'ECONNABORTED') {
await sleep(2000 * (intento + 1));
continue;
}
throw err;
}
}
throw new Error('Max retries excedidos');
}Patrón ❌ incorrecto
// MAL: generamos la key dentro del loop → cada reintento crea factura distinta
for (let i = 0; i < 3; i++) {
await axios.post('/api/v1/facturas', data, {
headers: { 'Idempotency-Key': crypto.randomUUID() } // ← bug!
});
}