Idempotencia

Cómo usar el encabezado Idempotency-Key para que un reintento de una emisión de documentos no genere un segundo documento ante la DIAN

Emitir un documento ante la DIAN es una operación irreversible: una vez aceptado, deshacerlo requiere emitir una anulación. Por eso un reintento del cliente —tras un timeout, un corte de red o un error en tu propio código— no debería producir una segunda emisión.

El encabezado Idempotency-Key te permite declarar que dos peticiones son la misma operación lógica. Cuando lo envías, la API garantiza que la operación se ejecute a lo sumo una vez para esa llave.

📘

Disponibilidad

El encabezado está disponible en todos los endpoints de emisión de documentos. El encabezado es opcional: si no lo envías, el comportamiento de la API no cambia en absoluto.

DocumentoEndpoints
Factura de ventaPOST /invoices
Nota créditoPOST /credit-notes
Nota débitoPOST /debit-notes
Documento soportePOST /support-documents
Nota de ajuste de documento soportePOST /adjustment-note-support-documents
Documento equivalente (POS)POST /equivalent-documents/pos
Nota de ajuste de documento equivalentePOST /adjustment-note-equivalent-documents
EventoPOST /events, POST /events/from-xml, POST /events/from-cufe
Nómina electrónicaPOST /payrolls, POST /payrolls/{id}/replace, POST /payrolls/{id}/cancel

La llave identifica una operación lógica dentro de un tipo de documento: la misma llave usada en POST /invoices y en POST /credit-notes son dos operaciones independientes. Los tres endpoints de eventos, en cambio, comparten el mismo espacio: son tres formatos de entrada para la misma operación lógica, así que reutilizar una llave entre ellos con un cuerpo distinto devuelve 422.

Cómo usarlo

  1. Genera un UUID v4 por cada operación lógica. El formato es obligatorio: la llave debe ser un UUID versión 4 en forma canónica con guiones (550e8400-e29b-41d4-a716-446655440000). Cualquier otro valor recibe 400. Se acepta en mayúsculas o minúsculas.
  2. Guárdala junto con la operación antes de enviar la petición, en el mismo lugar donde registras que ibas a emitir ese documento.
  3. Reutiliza exactamente la misma llave en todos los reintentos de esa operación. Una llave nueva significa una operación nueva y, por lo tanto, un documento nuevo.
📘

Por qué se exige UUID v4 y no cualquier cadena

Una llave derivada de tus datos (factura-2026-02-cliente-1234) parece única hasta el día en que la vuelves a generar para una operación que sí es nueva. Ahí recibirías la respuesta de la anterior en lugar de una emisión — y el síntoma sería un documento faltante, mucho más difícil de detectar que uno duplicado. Un v4 son 122 bits aleatorios: no se repite por accidente.

La mayoría de lenguajes lo traen de fábrica: crypto.randomUUID() en Node y navegadores, uuid.uuid4() en Python, UUID.randomUUID() en Java, Str::uuid() en Laravel.

curl -X POST https://api.alegra.com/e-provider/col/invoices \
  -H "Authorization: Basic <tu-token>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{ "number": 1, "resolution": { ... }, "company": { ... }, "customer": { ... }, "items": [ ... ] }'

Qué responde la API

SituaciónRespuestaQué hacer
Primera ejecuciónLa respuesta normal del endpointNada especial
Reintento de una operación ya resueltaEl mismo código de estado y cuerpo de la primera ejecución, con el encabezado Idempotent-Replayed: trueTratarla como la respuesta original
Otra petición con la misma llave sigue en curso409 con código IDEMPOTENCY_REQUEST_IN_PROGRESSReintentar en unos segundos con la misma llave
La llave se reutilizó con un cuerpo diferente422 con código IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOADRevisar tu integración: usa una llave nueva si de verdad es otra operación
La llave está mal formada400 con código IDEMPOTENCY_KEY_INVALIDCorregir el formato de la llave
No fue posible establecer el resultado de la petición original409 con código IDEMPOTENCY_OUTCOME_INDETERMINATEConsultar el estado del documento con el endpoint GET o el webhook antes de reintentar

Cómo se compara el cuerpo de la petición

La API calcula una huella del cuerpo enviado. El orden de las propiedades de un objeto y la representación numérica no la afectan: {"a":1,"b":2} y {"b":2,"a":1} son equivalentes, igual que 100.00 y 100.

En cambio el orden de los elementos de un arreglo sí importa, porque en los documentos el orden de los ítems, devengados o pagos tiene significado. Reordenar un arreglo produce una huella distinta y, por lo tanto, un 422.

⚠️

El caso que este mecanismo evita

Enviar el mismo documento dos veces con números consecutivos distintos produce dos documentos ante la DIAN, porque para la API son dos operaciones diferentes. Si ambas peticiones llevan la misma Idempotency-Key, la segunda recibe 422 y no se emite nada.

La respuesta almacenada es la de la petición original

La respuesta que se replica queda congelada en el momento de la primera ejecución. Si esa primera respuesta reportó WAITING_RESPONSE y el documento fue aceptado más tarde, un reintento seguirá reportando WAITING_RESPONSE: el encabezado Idempotent-Replayed: true es la señal de que estás viendo un resultado histórico.

Para conocer el estado actual de un documento usa siempre el endpoint GET correspondiente o el webhook de emisión, nunca un reintento.

La única excepción son las URLs de archivos: se firman de nuevo en cada respuesta, de modo que un reintento días después devuelve un enlace vigente y no uno vencido.

Si la generación de un enlace falla, el campo correspondiente dentro de files llega como null — nunca como un error: el documento sí fue emitido, y el enlace puede obtenerse consultando el documento con el endpoint GET correspondiente. Esto aplica tanto a las respuestas idempotentes como a las peticiones sin Idempotency-Key.

Vigencia

Las llaves se conservan 7 días. Pasado ese plazo, la misma llave vuelve a considerarse una operación nueva.