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:- Mutual TLS (mTLS) — The SOAP client presents your certificate when opening the HTTPS connection to AEAT. This proves your identity at the transport layer.
- 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.
CertificateManagerService.
Certificate Types
AEAT accepts two kinds of certificates for VERI*FACTU:- TYPE_CERTIFICATE (Personal / Company)
- TYPE_SEAL (Sello de Entidad)
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.SOAP endpoints used:
| Environment | URL |
|---|---|
| Production | https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP |
| Sandbox | https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP |
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 callVerifactu::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’sSoapClient requires the certificate and private key in a combined PEM file for mutual TLS. The library
creates this automatically using CertificateManagerService::createSoapCompatiblePemTemp():
- The PFX/P12 file is read and decrypted (using PHP’s
openssl_pkcs12_read, with a fallback to theopensslCLI with the-legacyflag for older RC2-40 encrypted certificates). - The X.509 certificate and private key are extracted as PEM blocks.
- They are concatenated and written to a temporary file with
0600permissions. - The temp file path is passed to
SoapClientas thelocal_certoption. - 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
| Format | Extension | Notes |
|---|---|---|
| PKCS#12 | .pfx, .p12 | Recommended. Most certificates issued by FNMT and other Spanish CAs use this format. |
| PEM | .pem | Supported. 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
Recommended: Environment Variables or a .env File
Store sensitive values outside your codebase using environment variables or a .env file (excluded from version
control via .gitignore):
.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::$issuerNifmust 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_SANDBOXis 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
opensslCLI is available on the server$PATH.
RuntimeException: “Certificate file not found”
- Verify the path in
VERIFACTU_CERT_PATHis an absolute path and the file exists. - Check that the PHP process has read permission on the certificate file.