Cómo se calcula la huella SHA-256 de VeriFactu, paso a paso

La huella es un resumen SHA-256 de ocho datos del registro de facturación, que incluye la huella del registro anterior. Así cada registro queda encadenado al anterior y cualquier cambio posterior se detecta.

Actualizada el 29 de septiembre de 2026 · Basada en el documento técnico de la AEAT sobre la huella (v0.1.2) y la Orden HAC/1177/2024.

1. Los 8 campos y su orden

Para un registro de alta (la factura que se emite), la huella se calcula con estos campos, siempre en este orden y con estos nombres:

#CampoQué esEjemplo
1IDEmisorFacturaNIF del emisor89890001K
2NumSerieFacturaSerie y número de la factura12345678/G33
3FechaExpedicionFacturaFecha de expedición01-01-2024
4TipoFacturaF1, F2, F3, R1…R5F1
5CuotaTotalSuma de las cuotas de IVA12.35
6ImporteTotalImporte total de la factura123.45
7HuellaHuella del registro anteriorvacío en el primero
8FechaHoraHusoGenRegistroMomento en que se genera el registro, con huso horario2024-01-01T19:20:30+01:00

2. El formato exacto de cada valor

La huella no se calcula sobre los datos, sino sobre el texto. Un solo carácter distinto produce una huella completamente diferente. Por eso:

3. El cálculo en 4 pasos

  1. Construye la cadena uniendo los 8 campos como nombre=valor, separados por &. No se codifica nada como URL: la barra de 12345678/G33 se queda como /.
  2. Pásala a bytes en UTF-8. Importa si la serie lleva caracteres como la ñ o acentos.
  3. Aplica SHA-256 a esos bytes.
  4. Escribe el resultado en hexadecimal en mayúsculas: 64 caracteres del 0 al 9 y de la A a la F.

La huella resultante es la que irá en el campo Huella del siguiente registro. Así se forma la cadena.

¿Qué es “el registro anterior”?

Es el último registro de facturación generado por el mismo sistema para ese emisor, sea de alta o de anulación. No es “la factura anterior de la misma serie”: si anulas una factura, la siguiente alta encadena con la anulación.

4. Los ejemplos de la AEAT, con su resultado

Estos son los casos del documento técnico de la AEAT. Si tu implementación no da exactamente estas huellas, hay un error de formato.

Caso 1: primer registro de alta (sin huella anterior)

IDEmisorFactura=89890001K&NumSerieFactura=12345678/G33&FechaExpedicionFactura=01-01-2024&TipoFactura=F1&CuotaTotal=12.35&ImporteTotal=123.45&Huella=&FechaHoraHusoGenRegistro=2024-01-01T19:20:30+01:00

Huella: 3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60

Caso 2: segundo registro de alta, encadenado con el primero

IDEmisorFactura=89890001K&NumSerieFactura=12345679/G34&FechaExpedicionFactura=01-01-2024&TipoFactura=F1&CuotaTotal=12.35&ImporteTotal=123.45&Huella=3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60&FechaHoraHusoGenRegistro=2024-01-01T19:20:35+01:00

Huella: F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97

Puedes comprobar ambos casos, y los tuyos, en el validador: calcula la huella en tu navegador y no envía los datos a ningún servidor.

5. La huella de un registro de anulación

Cuando se anula una factura ya registrada, el registro de anulación tiene su propia huella, con cinco campos:

  1. IDEmisorFacturaAnulada
  2. NumSerieFacturaAnulada
  3. FechaExpedicionFacturaAnulada
  4. Huella (la del registro anterior de la cadena)
  5. FechaHoraHusoGenRegistro

Caso 3: anulación de la factura del caso 2

IDEmisorFacturaAnulada=89890001K&NumSerieFacturaAnulada=12345679/G34&FechaExpedicionFacturaAnulada=01-01-2024&Huella=F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97&FechaHoraHusoGenRegistro=2024-01-01T19:20:40+01:00

Huella: 177547C0D57AC74748561D054A9CEC14B4C4EA23D1BEFD6F2E69E3A388F90C68

6. Código en JavaScript, PHP y Python

JavaScript (navegador o Node.js 18+)

async function huellaAlta(c) {
  const cadena =
    `IDEmisorFactura=${c.nif.trim()}` +
    `&NumSerieFactura=${c.numSerie.trim()}` +
    `&FechaExpedicionFactura=${c.fecha.trim()}` +
    `&TipoFactura=${c.tipo.trim()}` +
    `&CuotaTotal=${c.cuota.trim()}` +
    `&ImporteTotal=${c.importe.trim()}` +
    `&Huella=${(c.huellaAnterior || '').trim()}` +
    `&FechaHoraHusoGenRegistro=${c.fechaHora.trim()}`;
  const bytes = new TextEncoder().encode(cadena);          // UTF-8
  const digest = await crypto.subtle.digest('SHA-256', bytes);
  return [...new Uint8Array(digest)]
    .map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();
}

PHP

function huella_alta(array $c): string {
    $cadena = 'IDEmisorFactura=' . trim($c['nif'])
        . '&NumSerieFactura=' . trim($c['num_serie'])
        . '&FechaExpedicionFactura=' . trim($c['fecha'])
        . '&TipoFactura=' . trim($c['tipo'])
        . '&CuotaTotal=' . trim($c['cuota'])
        . '&ImporteTotal=' . trim($c['importe'])
        . '&Huella=' . trim($c['huella_anterior'] ?? '')
        . '&FechaHoraHusoGenRegistro=' . trim($c['fecha_hora']);
    return strtoupper(hash('sha256', $cadena)); // el texto PHP ya es UTF-8
}

Python

import hashlib

def huella_alta(c: dict) -> str:
    cadena = (
        f"IDEmisorFactura={c['nif'].strip()}"
        f"&NumSerieFactura={c['num_serie'].strip()}"
        f"&FechaExpedicionFactura={c['fecha'].strip()}"
        f"&TipoFactura={c['tipo'].strip()}"
        f"&CuotaTotal={c['cuota'].strip()}"
        f"&ImporteTotal={c['importe'].strip()}"
        f"&Huella={c.get('huella_anterior', '').strip()}"
        f"&FechaHoraHusoGenRegistro={c['fecha_hora'].strip()}"
    )
    return hashlib.sha256(cadena.encode("utf-8")).hexdigest().upper()

Con los datos del caso 1, las tres funciones devuelven 3C464DAF…F12F60. Si tu resultado no coincide, revisa los errores más habituales.

La huella es solo una parte

Calcular bien la huella no basta para cumplir VeriFactu. El sistema también debe generar el registro de facturación en el formato XML de la AEAT, enviarlo a la AEAT (modalidad VERI*FACTU) o firmarlo y llevar un registro de eventos (modalidad no VERI*FACTU), incluir el código QR en la factura y contar con la declaración responsable del fabricante del software.

Comprueba tu huella en segundos

El validador calcula la huella y la URL del QR de cualquier registro, marca los errores de formato y trae cargados los ejemplos de la AEAT. Funciona en tu navegador: los datos no salen de tu equipo.

Abrir el validador¿Tienes WooCommerce?