QAPI está en beta — la API puede cambiar sin previo aviso. Usa sandbox para pruebas.
QAPIQAPIDocs · by DTEVIA
Guías

Idempotencia e Integridad

Cómo usar Idempotency-Key para evitar facturas duplicadas y verificar integridad con checksums.

Idempotencia e Integridad

QAPI ofrece dos mecanismos para garantizar confiabilidad en tu integración: idempotencia para evitar duplicados y checksums para verificar integridad.


Idempotencia

Cuando tu servidor envía un request pero la red falla antes de recibir respuesta, ¿emitiste la factura o no? Sin idempotencia, reenviar el request podría crear un duplicado con validez tributaria.

Cómo usar Idempotency-Key

Envía un identificador único en el header Idempotency-Key con cada request de creación (POST /v1/invoices y POST /v1/sandbox/invoices). Puede ser un UUID o el ID de tu orden — cualquier string ASCII sin espacios de 1 a 200 caracteres:

curl -X POST https://api.dtevia.com.gt/v1/invoices \
  -H "Authorization: Bearer qapi_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ORD-2026-001" \
  -d @factura.json

Si envías el mismo Idempotency-Key dos veces con el mismo body, QAPI no crea una segunda factura: devuelve exactamente la misma respuesta que el request original (mismo id) con el header Idempotency-Replayed: true. La ventana de idempotencia es de 24 horas y aplica por tenant.

Por qué esto importa en facturación

QAPI genera un numero_acceso aleatorio por request, así que dos requests idénticos sin Idempotency-Key producen dos DTEs distintos y válidos — el anti-duplicados del certificador no puede detectarlos. La única protección real contra duplicados por reintentos de red es este header.

Genera la key en tu código

import { randomUUID } from 'crypto';

async function emitirFacturaIdempotente(payload: InvoicePayload, orderId: string) {
  // Usa un deterministic ID basado en tu orden para sobrevivir reinicios del servidor
  const idempotencyKey = orderId; // o randomUUID() si no tienes un ID propio

  const res = await fetch('https://api.dtevia.com.gt/v1/invoices', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.QAPI_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify(payload),
  });

  return res.json();
}
import uuid

def emitir_factura_idempotente(payload: dict, order_id: str) -> dict:
    idempotency_key = order_id  # o str(uuid.uuid4())

    res = httpx.post(
        "https://api.dtevia.com.gt/v1/invoices",
        json=payload,
        headers={
            "Authorization": f"Bearer {QAPI_API_KEY}",
            "Idempotency-Key": idempotency_key,
        },
    )
    return res.json()

Comportamiento en reintentos

EscenarioComportamiento
Timeout antes de recibir respuestaReenvía con la misma key → obtienes la respuesta original (header Idempotency-Replayed: true)
El request original dio error (422, 5xx)La key queda libre: corrige el payload y reintenta con la misma key (solo se guardan respuestas exitosas)
Misma key, body distinto409 IDEMPOTENCY_CONFLICT — usa una key nueva para un request nuevo
Dos requests simultáneos con la misma keyEl segundo recibe 409 IDEMPOTENCY_CONFLICT con Retry-After
Key diferente, mismo payloadQAPI crea una segunda factura distinta
Key expirada (>24h)QAPI trata el request como nuevo

Integridad de respuestas

QAPI firma todas las respuestas con un checksum SHA-256 para que puedas detectar manipulación en tránsito.

Headers de integridad

Prop

Type

Asertar el checksum del request (opcional)

Si querés que QAPI verifique que tu request llegó intacto, calculá el SHA-256 (hex) del body que enviás y mandalo en el header X-Content-SHA256. QAPI compara ese valor con el hash del cuerpo recibido y, si no coincide, rechaza el request con 400 INTEGRITY_CHECK_FAILED antes de procesarlo.

BODY=$(cat factura.json)
HASH=$(printf '%s' "$BODY" | sha256sum | cut -d' ' -f1)

curl -X POST https://api.dtevia.com.gt/v1/invoices \
  -H "Authorization: Bearer qapi_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "X-Content-SHA256: $HASH" \
  -d "$BODY"

El header es opcional: si no lo envías, QAPI no valida el hash del request (igual responde con X-Request-Checksum, el SHA-256 del body que recibió). La comparación es sobre el hash en minúsculas, calculado sobre los bytes exactos del body que mandás.

Verificar el checksum de respuesta

import crypto from 'crypto';

async function fetchWithIntegrityCheck(url: string, options: RequestInit) {
  const res = await fetch(url, options);
  const rawBody = await res.text();
  const receivedChecksum = res.headers.get('x-response-checksum');

  if (receivedChecksum) {
    const calculatedChecksum = crypto
      .createHash('sha256')
      .update(rawBody)
      .digest('hex');

    if (calculatedChecksum !== receivedChecksum) {
      throw new Error('Integridad comprometida: checksum no coincide');
    }
  }

  return JSON.parse(rawBody);
}
import hashlib

def fetch_with_integrity_check(url: str, **kwargs) -> dict:
    res = httpx.get(url, **kwargs)
    raw_body = res.text
    received_checksum = res.headers.get("x-response-checksum")

    if received_checksum:
        calculated = hashlib.sha256(raw_body.encode()).hexdigest()
        if calculated != received_checksum:
            raise ValueError("Integridad comprometida: checksum no coincide")

    return res.json()

integrity_checksum en la factura

Además de los headers HTTP, cada factura certificada incluye un campo integrity_checksum en el objeto de respuesta:

{
  "id": "inv_V1StGXR8_Z5jdHi6",
  "status": "CERTIFIED",
  "integrity_checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "uuid_sat": "550E8400-E29B-41D4-A716-446655440000"
}

Es el SHA-256 (hex, sin prefijo) del body JSON original con el que creaste la factura. Puedes recalcularlo sobre el payload que enviaste para demostrar que lo que QAPI registró es exactamente lo que tu sistema emitió.


Resumen de buenas prácticas

  1. Siempre usa Idempotency-Key en POST /v1/invoices — usa el ID de tu orden como key
  2. Guarda el id QAPI junto a tu orden inmediatamente al recibir el 202
  3. Verifica X-Response-Checksum en entornos de alta seguridad
  4. Almacena integrity_checksum de cada factura certificada como evidencia de integridad

On this page