Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Eseperio/verifactu-php/llms.txt
Use this file to discover all available pages before exploring further.
What Is Hash Chaining?
Every invoice record submitted to AEAT must include a SHA-256 hash called the huella (fingerprint). The huella of each invoice is computed from the invoice’s own fields plus the huella of the immediately preceding invoice. This creates a tamper-evident chain: changing any record in the sequence invalidates the hash of every record that follows it, making retroactive alterations detectable by AEAT.Huella field in the concatenation string is an empty
string.
The library calculates the huella automatically inside
Verifactu::registerInvoice() and
Verifactu::cancelInvoice(). You do not need to call HashGeneratorService::generate() yourself for normal
submissions — just set up the Chaining block correctly and the library takes care of the rest.The Chaining Model
Every InvoiceSubmission and InvoiceCancellation must include a Chaining object set via setChaining().
The Chaining model has two mutually exclusive states:
| State | When to use |
|---|---|
firstRecord = 'S' | This is the first invoice your system ever submits to AEAT (or the first after a voluntary VERI*FACTU stop). |
setPreviousInvoice(...) | This invoice follows another one — provide the previous invoice’s identity and hash. |
First Invoice in the Chain
Subsequent Invoices
For every invoice after the first, supply the previous invoice’s issuer NIF, series number, issue date, and hash:setPreviousInvoice() also accepts a plain array for convenience:
Setting a previous invoice via
setPreviousInvoice() automatically clears firstRecord, and calling
setAsFirstRecord() automatically clears previousInvoice. The model’s validate() method enforces
that exactly one of the two is set — both null or both non-null will fail validation.The Huella Concatenation Format
Invoice Submission (RegistroAlta)
For an InvoiceSubmission, HashGeneratorService concatenates the following fields in this exact order,
separated by &:
| Concatenation field | Source | Notes |
|---|---|---|
IDEmisorFactura | InvoiceId::$issuerNif | Trimmed string |
NumSerieFactura | InvoiceId::$seriesNumber | Trimmed string |
FechaExpedicionFactura | InvoiceId::$issueDate | Format: DD-MM-YYYY |
TipoFactura | InvoiceSubmission::$invoiceType | Enum value (e.g. F1) |
CuotaTotal | InvoiceSubmission::$taxAmount | Normalised decimal — see below |
ImporteTotal | InvoiceSubmission::$totalAmount | Normalised decimal — see below |
Huella | PreviousInvoiceChaining::$hash (or "" for first) | Previous record’s huella |
FechaHoraHusoGenRegistro | InvoiceRecord::$recordTimestamp | ISO 8601 with timezone |
Invoice Cancellation (RegistroAnulacion)
For an InvoiceCancellation, the concatenation is shorter and uses different field names:
Decimal Normalisation
Monetary values (CuotaTotal and ImporteTotal) are normalised using number_format() with exactly two
decimal places and a dot as the decimal separator. No thousands separator is used.
| Raw value | Normalised |
|---|---|
21 | 21.00 |
121.5 | 121.50 |
1000.123 | 1000.12 |
0 | 0.00 |
HashGeneratorService::normalizeDecimal(). Make sure the
taxAmount and totalAmount fields on your InvoiceSubmission are set to numeric values (int or float)
before calling registerInvoice().
The recordTimestamp Field
The recordTimestamp field (FechaHoraHusoGenRegistro) is part of the hash input. It must be a valid ISO 8601
datetime string with an explicit timezone offset — never UTC Z notation.
+01:00 in winter, +02:00 in summer):
How the Auto-Generated Hash Flows Through the Chain
Here is the complete sequence for two consecutive invoices:registerInvoice(), retrieve the generated hash from $invoice->hash and persist it
alongside your invoice record. You will need it to build the Chaining block for the next invoice.
Advanced: Calling HashGeneratorService Directly
In most cases you should let the library handle hashing automatically. However, there are legitimate advanced
scenarios where you might need to compute the huella separately — for example:
- Verifying the hash of a historical record stored in your database
- Pre-computing hashes in a batch job before the final submission
- Writing custom tests that check your field values produce the expected hash
HashGeneratorService::generate() returns the hash as an uppercase hexadecimal string (64 characters),
matching the format AEAT expects in the Huella XML element and in subsequent chaining fields.