Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/josemmo/Verifactu-PHP/llms.txt

Use this file to discover all available pages before exploring further.

A CancellationRecord (registro de anulación) is used to annul a registration record that was previously submitted to the AEAT. It references the original invoice by its identifier and must be chained to the previous record in your SIF’s billing sequence, just like any other record.
Cancellation records should only be used in very specific cases and are rarely the right tool. If you need to “cancel” an invoice — for example because it contained incorrect amounts — the correct approach is to issue a corrective invoice (factura rectificativa) and report it as a new registration record with negative amounts. Cancellation records are for annulling the registration itself, not for correcting invoice data.For example:
  1. Invoice FAC2026/123 was issued for €100.00 and needs to be cancelled.
  2. Issue corrective invoice REC2026/001 for −€100.00.
  3. Create and submit a RegistrationRecord for REC2026/001.
1

Create the InvoiceIdentifier for the invoice to cancel

Provide the InvoiceIdentifier that matches the original registration record exactly — the same issuer NIF, invoice number, and issue date.
use DateTimeImmutable;
use josemmo\Verifactu\Models\Records\InvoiceIdentifier;

$invoiceId                = new InvoiceIdentifier();
$invoiceId->issuerId      = '89890001K';
$invoiceId->invoiceNumber = '12345679/G34';
$invoiceId->issueDate     = new DateTimeImmutable('2024-01-01');
2

Create the CancellationRecord and assign the invoice ID

Instantiate a CancellationRecord and assign the invoice identifier from the previous step.
use josemmo\Verifactu\Models\Records\CancellationRecord;

$record            = new CancellationRecord();
$record->invoiceId = $invoiceId;
3

Chain to the previous record

Unlike RegistrationRecord (where the very first record can have null predecessors), all CancellationRecord instances require a previousInvoiceId and previousHash. Both fields are mandatory — validation will fail if either is null.
$record->previousInvoiceId                = new InvoiceIdentifier();
$record->previousInvoiceId->issuerId      = '89890001K';
$record->previousInvoiceId->invoiceNumber = '12345679/G34';
$record->previousInvoiceId->issueDate     = new DateTimeImmutable('2024-01-01');

$record->previousHash = 'F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97';
4

Calculate the hash

Record the generation timestamp and compute the SHA-256 hash. For cancellation records the hash payload uses the cancellation-specific field names (IDEmisorFacturaAnulada, NumSerieFacturaAnulada, FechaExpedicionFacturaAnulada), but calculateHash() handles this automatically.
use DateTimeImmutable;

$record->hashedAt = new DateTimeImmutable();
$record->hash     = $record->calculateHash();
5

Validate the record

Call validate() to confirm the record is well-formed before sending it to the AEAT.
use josemmo\Verifactu\Exceptions\InvalidModelException;

try {
    $record->validate();
    echo "Cancellation record is valid.\n";
} catch (InvalidModelException $e) {
    echo "Validation failed: $e\n";
}

Complete working example

<?php
use DateTimeImmutable;
use josemmo\Verifactu\Exceptions\InvalidModelException;
use josemmo\Verifactu\Models\Records\CancellationRecord;
use josemmo\Verifactu\Models\Records\InvoiceIdentifier;

// 1. Invoice to cancel
$invoiceId                = new InvoiceIdentifier();
$invoiceId->issuerId      = '89890001K';
$invoiceId->invoiceNumber = '12345679/G34';
$invoiceId->issueDate     = new DateTimeImmutable('2024-01-01');

// 2. Cancellation record
$record            = new CancellationRecord();
$record->invoiceId = $invoiceId;

// 3. Chain (required for all cancellation records)
$record->previousInvoiceId                = new InvoiceIdentifier();
$record->previousInvoiceId->issuerId      = '89890001K';
$record->previousInvoiceId->invoiceNumber = '12345679/G33'; // the record before this one
$record->previousInvoiceId->issueDate     = new DateTimeImmutable('2024-01-01');
$record->previousHash                     = 'F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97';

// 4. Hash
$record->hashedAt = new DateTimeImmutable();
$record->hash     = $record->calculateHash();

// 5. Validate
try {
    $record->validate();
    echo "Record is valid.\n";
} catch (InvalidModelException $e) {
    echo "Validation failed: $e\n";
}

The withoutPriorRecord flag

The boolean property $withoutPriorRecord (default: false) is used when you need to cancel a record that does not exist in the AEAT system or in the SIF itself — for example, when annulling a record that was generated locally but never reached AEAT, or that was removed from the SIF’s internal store.
$record->withoutPriorRecord = true;
Set this flag to true only when the AEAT (or your own SIF) has no knowledge of the original registration record. In the normal cancellation flow the flag should remain false.

The isPriorRejection flag

If a cancellation record was previously submitted and rejected by AEAT, you can resubmit it with the isPriorRejection flag set to true. This signals to AEAT that this is a re-submission of a record that was rejected in the immediately preceding batch.
$record->isPriorRejection = true;
Unlike the same property on RegistrationRecord, isPriorRejection on CancellationRecord is a plain bool (no null variant) and defaults to false.

Build docs developers (and LLMs) love