VeriFactu in a real ERP: how we submit invoices to the Spanish Tax Agency
How we integrate billing record submission to the Spanish Tax Agency (AEAT) in a production ERP: architecture, chained hash, queues, and real-world errors.
Draft. This document outlines the technical architecture and proposed structure. Sections marked with [To complete] will be detailed with specific production telemetry before final editorial publication.
Context and scope
[To complete] Target ERP architecture, monthly invoice volume, billing modules, and regulatory triggers. Link to applicable Royal Decrees and check mandatory enforcement deadlines (statutory timelines have evolved).
What the regulatory standard actually mandates
Before writing application code, understand the core technical requirements:
- Every invoice generates a billing record (registration, and cancellation when applicable) that the system persists immutably and dispatches.
- Records are cryptographically chained using a hash that incorporates the previous record’s hash, ensuring retroactive tampering is mathematically detectable.
- The generated PDF or printed invoice displays a QR code enabling customers to verify invoice submission directly with the tax authority.
- Telemetry dispatch connects to an AEAT web service authenticated via mutual TLS (mTLS) with an approved software certificate.
[To complete] Cross-reference each technical item against the latest official technical specification, citing exact document version and schema revision.
Integration architecture
[To complete] Component diagram: ERP core, record generator, persistent dispatch queue, SOAP/REST web service client, and response repository.
Our primary design decision was decoupling invoice emission from upstream tax dispatch: invoices are finalized and recorded locally immediately, while network transmission is handled asynchronously via resilient worker queues.
Record generation and cryptographic chaining
Simplified conceptual example demonstrating hash chaining with the preceding record. This is not the official schema: exact field ordering and formatting are defined in the AEAT technical specifications.
import { createHash } from "node:crypto";
interface BillingRecord {
invoiceId: string;
totalAmount: string;
previousHash: string; // empty string for the genesis record in a series
}
export function computeHash(r: BillingRecord): string {
const payload = `${r.invoiceId}|${r.totalAmount}|${r.previousHash}`;
return createHash("sha256").update(payload, "utf8").digest("hex").toUpperCase();
}
[To complete] Concurrency management strategy preventing race conditions from corrupting hash sequences (row-level locking, per-series sequence generators, database isolation).
Dispatch queue and retry backoff
[To complete] Lifecycle states of a billing record (pending, dispatched, accepted, rejected), exponential backoff retry policies, and operational failover procedures when AEAT endpoints timeout.
Production error codes and resolutions
[To complete] Structured table of frequent schema rejection codes, underlying root causes, and practical fixes—the most valuable section for fellow engineering teams.
Testing and staging environments
[To complete] Using the official AEAT testing sandbox, test certificates, synthetic transaction data, and disaster recovery rollback plans.
What we would architect differently
[To complete] Three architectural lessons learned in production.
Conclusions
[To complete] Brief summary and call to action: if you need to integrate VeriFactu compliance into your custom ERP or billing software, get in touch.
Related articles
Custom software or off-the-shelf product: how to decide without regrets
Four questions to work out whether your company needs custom software or an off-the-shelf product is enough, and what a wrong call costs.
