Skip to main content

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.

Why Certificates Are Required

VERI*FACTU submissions must be authenticated at two levels:
  1. Mutual TLS (mTLS) — The SOAP client presents your certificate when opening the HTTPS connection to AEAT. This proves your identity at the transport layer.
  2. XAdES XML signature — The invoice XML is digitally signed with your private key before it is sent. This proves the integrity and origin of the invoice data at the application layer.
Both uses draw from the same PFX/P12 certificate file. The library handles all the low-level certificate operations for you via CertificateManagerService.

Certificate Types

AEAT accepts two kinds of certificates for VERI*FACTU:
A standard X.509 certificate issued to a natural person or a legal entity (company). This is the most common type used by businesses managing their own invoices.
use eseperio\verifactu\Verifactu;

Verifactu::config(
    '/path/to/company-certificate.pfx',
    'certificate-password',
    Verifactu::TYPE_CERTIFICATE,           // Personal or company certificate
    Verifactu::ENVIRONMENT_PRODUCTION
);
SOAP endpoints used:
EnvironmentURL
Productionhttps://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
Sandboxhttps://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
The Verifactu::config() method resolves the correct SOAP endpoint for you based on both the certificate type and the environment — you never need to set a URL manually.

How the Library Uses Your Certificate

When you call Verifactu::config(), the certificate path and password are stored in VerifactuService’s static configuration. The certificate is not loaded until the first SOAP call is made.

Creating a SOAP-Compatible PEM File

PHP’s SoapClient requires the certificate and private key in a combined PEM file for mutual TLS. The library creates this automatically using CertificateManagerService::createSoapCompatiblePemTemp():
  1. The PFX/P12 file is read and decrypted (using PHP’s openssl_pkcs12_read, with a fallback to the openssl CLI with the -legacy flag for older RC2-40 encrypted certificates).
  2. The X.509 certificate and private key are extracted as PEM blocks.
  3. They are concatenated and written to a temporary file with 0600 permissions.
  4. The temp file path is passed to SoapClient as the local_cert option.
  5. A register_shutdown_function() call ensures the temporary file is deleted when the PHP process ends.

XML Signing

For each invoice submission, XmlSignerService calls CertificateManagerService::getCertificate() and CertificateManagerService::getPrivateKey() to load the certificate and key separately for the XAdES signing process.

Supported Certificate Formats

FormatExtensionNotes
PKCS#12.pfx, .p12Recommended. Most certificates issued by FNMT and other Spanish CAs use this format.
PEM.pemSupported. Must contain both the certificate block and (optionally) the private key. Encrypted PEM keys are supported when a password is provided.
If your OpenSSL installation does not support legacy ciphers (common with OpenSSL 3 without the legacy provider), the library automatically falls back to calling the openssl CLI binary with the -legacy flag. Set the OPENSSL_BIN environment variable to override the path to the openssl binary if it is not on your $PATH.

Keeping Certificates Secure

Never hardcode certificate paths or passwords in your source code. Certificates and their passwords grant legal authority to submit invoices to AEAT on your behalf. A leaked certificate could be used to submit fraudulent invoices under your identity.
Store sensitive values outside your codebase using environment variables or a .env file (excluded from version control via .gitignore):
# .env  — never commit this file
VERIFACTU_CERT_PATH=/secure/path/to/certificate.p12
VERIFACTU_CERT_PASSWORD=your_certificate_password
VERIFACTU_CERT_TYPE=certificate
VERIFACTU_ENVIRONMENT=production
Then load them in your application bootstrap:
use eseperio\verifactu\Verifactu;

Verifactu::config(
    getenv('VERIFACTU_CERT_PATH'),
    getenv('VERIFACTU_CERT_PASSWORD'),
    getenv('VERIFACTU_CERT_TYPE') === 'seal'
        ? Verifactu::TYPE_SEAL
        : Verifactu::TYPE_CERTIFICATE,
    getenv('VERIFACTU_ENVIRONMENT') === 'sandbox'
        ? Verifactu::ENVIRONMENT_SANDBOX
        : Verifactu::ENVIRONMENT_PRODUCTION
);
If you use a framework with built-in .env support (Laravel, Symfony, etc.), use its native env-loading mechanism rather than calling getenv() directly.

File System Security

  • Store the certificate file outside the web root so it cannot be served over HTTP.
  • Set file permissions to 0600 (owner read/write only) on Linux/macOS.
  • On shared hosting, consider encrypting the certificate file at rest.

Troubleshooting Certificate Errors

AEAT SOAP Error Code 100 — “SOAP request signature is not valid”

This error means AEAT rejected the XML signature on the request. Common causes:
  • The certificate has expired — check with CertificateManagerService::isValid($certPath, $certPassword).
  • The certificate does not match the issuer NIF in the invoice — the NIF in InvoiceId::$issuerNif must exactly match the NIF embedded in the certificate and registered in your AEAT census data.
  • The wrong certificate type was used for the selected endpoint (e.g. a seal certificate pointing at the non-seal URL).

AEAT SOAP Error Code 106 — “Certificate is on a blocklist or is a test certificate”

This error means:
  • You are using a test/sandbox certificate against the production endpoint (or vice versa). Ensure ENVIRONMENT_SANDBOX is selected when using a test certificate.
  • The certificate serial number has been revoked or added to the AEAT blocklist. Contact your certificate authority (CA) for a replacement.

RuntimeException: “Unable to read PFX/P12 certificate”

  • Double-check the password — it is case-sensitive.
  • If your certificate uses the legacy RC2-40 cipher (common with older FNMT certificates), ensure either the OpenSSL legacy provider is installed, or the openssl CLI is available on the server $PATH.

RuntimeException: “Certificate file not found”

  • Verify the path in VERIFACTU_CERT_PATH is an absolute path and the file exists.
  • Check that the PHP process has read permission on the certificate file.

Build docs developers (and LLMs) love