Skip to content

Repository files navigation

pyverifactu-huella

The hash and chaining of VERI*FACTU billing records, in pure Python with zero dependencies.

tests Python 3.9+ License MIT Dependencies Typed

🇬🇧 English · 🇪🇸 Léeme en español

If your software issues invoices in Spain, before 2027 it must produce a billing record for every invoice, carrying a SHA-256 hash chained to the previous record's hash. This library does exactly that part, and reproduces the Spanish Tax Agency's official test vector in its test suite.

from verifactu_huella import huella_alta

huella_alta(
    id_emisor_factura="89890001K",
    num_serie_factura="12345678/G33",
    fecha_expedicion_factura="01-01-2024",
    tipo_factura="F1",
    cuota_total="12.35",
    importe_total="123.45",
    huella_anterior="",  # empty on the first record
    fecha_hora_huso_gen_registro="2024-01-01T19:20:30+01:00",
)
# '3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60'

That value is the one the AEAT publishes as example 6.1 of its specification. It is pinned in tests/test_huella.py: if it ever stops matching, CI turns red.

A note on the API language. Field names are in Spanish on purpose — they are the literal names from the AEAT's record layouts (IDEmisorFactura, NumSerieFactura, …). Renaming them would force you to keep a translation table in your head while reading the official spec next to your code. Documentation is in English; the wire format is not ours to translate.

On the name. The distribution is pyverifactu-huella; the import stays verifactu_huella, following the usual convention (pyyamlyaml, pyserialserial). The prefix is deliberate: "Verifactu" is a Spanish registered trademark held by a private company — not by the Tax Agency — so the term is used here only descriptively, to say what the library computes.

Install

pip install git+https://github.com/mindset-code/pyverifactu-huella

Python 3.9 or newer. No dependencies at all: only hashlib and decimal from the standard library. It drops into an existing project without arguing with anyone's version pins.

Try it without writing code

The package ships a command-line interface, so you can check a hash straight after cloning:

# The official AEAT test vector, verified end to end
python -m verifactu_huella autocomprobar

# Hash a single "alta" record
python -m verifactu_huella alta \
    --id-emisor-factura 89890001K \
    --num-serie-factura 12345678/G33 \
    --fecha-expedicion-factura 01-01-2024 \
    --tipo-factura F1 \
    --cuota-total 12.35 \
    --importe-total 123.45 \
    --fecha-hora-huso-gen-registro 2024-01-01T19:20:30+01:00

# Chain a whole series from a JSON file and verify every link
python -m verifactu_huella encadenar examples/serie.json --verificar

autocomprobar exits with status 0 when the computed hash matches the AEAT's published value and 1 when it does not — which makes it usable as a smoke test in your own deployment pipeline.

Chaining a series

What usually breaks in an integration is not computing one hash: it is keeping the chain intact over time. Encadenador holds the previous record's hash and injects it for you:

from verifactu_huella import Encadenador

cadena = Encadenador()

first = cadena.alta(
    id_emisor_factura="89890001K",
    num_serie_factura="12345678/G33",
    fecha_expedicion_factura="01-01-2024",
    tipo_factura="F1",
    cuota_total="12.35",
    importe_total="123.45",
    fecha_hora_huso_gen_registro="2024-01-01T19:20:30+01:00",
)

second = cadena.alta(
    id_emisor_factura="89890001K",
    num_serie_factura="12345679/G33",
    fecha_expedicion_factura="02-01-2024",
    tipo_factura="F1",
    cuota_total="21.00",
    importe_total="121.00",
    fecha_hora_huso_gen_registro="2024-01-02T09:15:00+01:00",
)

second.huella_anterior == first.huella  # True
cadena.verificar()  # raises if any link does not hold

To resume a series that already exists in your database:

cadena = Encadenador(
    huella_previa="3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60"
)

A malformed hash — length other than 64, lowercase, or non-hex characters — is rejected up front rather than chained onto a corrupt value and discovered months later.

What it covers

Record Function Fields
Issue (alta) huella_alta() 8
Cancellation (anulación) huella_anulacion() 5
Event (evento) huella_evento() 9

All three respect the field order the specification fixes, which is not alphabetical and not arbitrary: it is the order in which fields appear in the record layouts.

The details that trip people up

This is where a home-grown implementation drifts from what the AEAT expects. Every one of these is covered by a test:

  • An empty field is not omitted. Its name and the = still appear, with no value. In a system's first record the string carries &Huella=& in the middle, not &&.
  • Trailing zeros do not count. 123.1 and 123.10 produce the same hash. The library normalises to a single representation, so the result does not depend on how your ORM formats decimals.
  • Whitespace is trimmed at each end of a value, never in the middle: 12345678 / G33 keeps its interior spaces.
  • Output is uppercase, 64 hex characters. Python's hexdigest() returns lowercase.
  • In an event record, NIF appears twice: the software system's and the one of the party obliged to issue. That is not a typo in the specification.
  • NIF and ID of the software system are mutually exclusive. You fill in one and the other travels empty; the library enforces it instead of letting you build a hash the AEAT will reject.

Deadlines

Per the AEAT's information note following Royal Decree-Law 15/2025 of 2 December:

Obliged party Adaptation deadline
Entities filing Corporate Income Tax 1 January 2027
All other obliged taxpayers 1 July 2027

Scope, and when to use something else

This library covers the hash and the chaining. It does not sign electronically, does not build the full XML and does not send anything to the AEAT.

That is deliberate, but it is not always what you need:

  • To build the whole flow — certificate signing, SOAP over mTLS, submission to the web service — there are SDKs that already do it. In Python, verifactu by jalvarezz13; in C#, mdiago/VeriFactu; in PHP, josemmo/Verifactu-PHP.
  • If submission is already handled and what you want is a small, auditable, dependency-free piece that computes the hash — or one to cross-check the hash you already compute — this library is exactly that.

This is not tax advice. Check the specification in force before putting a billing system into production.

Documentation

What this library does not do

Stated plainly, because it is what determines your obligations rather than ours:

  • ❌ Does not sign electronically (XAdES) — needs a certificate and a crypto stack
  • ❌ Does not build the full XML billing record
  • ❌ Does not submit anything to the AEAT, and makes no network calls at all
  • ❌ Does not keep or retain records
  • ❌ Does not generate the invoice QR code
  • ✅ Computes the hash and maintains the chain. That is the whole scope.

Legal notice and disclaimer

Not official software. This is an independent open-source contribution. It is not software of the Spanish Tax Agency (AEAT), of any public body, or of the holder of any trademark containing the term "Verifactu".

Not endorsed or certified. The AEAT does not approve, certify or register invoicing software: conformity is established by a responsible declaration made by the producer itself (art. 13 of RD 1007/2023 and art. 15 of Order HAC/1177/2024; see the official FAQ). No mention of the AEAT anywhere in this repository implies its participation, sponsorship or support.

Not an invoicing system. This library is published without a responsible declaration, because it does not by itself constitute a Sistema Informático de Facturación (SIF) within the meaning of art. 1.2 of RD 1007/2023: it does not issue invoices, does not retain records, does not sign, does not build the complete XML and does not transmit anything. It is a tool for building an SIF, not one.

The responsible declaration is yours. If you integrate this code into an invoicing system, the obligation to issue the responsible declaration falls on you, as producer or integrator of that system. The AEAT says so expressly: "the company that programs the code or integrates parts of other software, whether open source or not, must make the responsible declaration… where it uses open source, it becomes responsible for how it works". Auditing this code and verifying its conformity is your responsibility.

Not tax advice. Tax obligations rest with the taxpayer. Consult a qualified professional before putting this software into production.

No warranty. No warranty is given as to correctness, completeness, fitness for a particular purpose, or conformity with any version of the VERI*FACTU rules. Always check your results against the official specification and the AEAT's own test environment. The software is provided "AS IS" under the MIT licence.

Source of the specification. This implements "Detalle de las especificaciones técnicas para la generación de la huella o hash de los registros de facturación", AEAT, version 0.1.2, 27 August 2024, published on the AEAT developer portal. Reused under the AEAT legal notice, Law 37/2007 and RD 1495/2011.

Trademarks. "VERI*FACTU" is the name of the regime established by RD 1007/2023 and Order HAC/1177/2024. Any third-party registered trademark containing that term belongs to its respective owner; its use here is purely descriptive, to indicate what the software is for, under art. 37 of Spanish Trademark Act 17/2001.

Sources

The whole implementation comes from official, public documentation:

Contributing

See CONTRIBUTING.md. Bugs and proposals go to issues. If you send a behaviour change, bring a test with it: the suite is the only guarantee that the hash still matches the Tax Agency's.

git clone https://github.com/mindset-code/pyverifactu-huella
cd pyverifactu-huella
pip install -e ".[dev]"
pytest -v

License

MIT — see LICENSE.


Maintained by Mindset & Code. If you need to integrate VERI*FACTU into a system that is already running, or to review an integration you already have, write to contacto@mindset-code.com.

About

Huella y encadenamiento de registros de facturación VERI*FACTU (AEAT) en Python puro, sin dependencias. Validado contra el vector oficial de la Agencia Tributaria.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages