Resumen
El QR contiene una URL de la AEAT con cuatro parámetros (nif, numserie, fecha, importe) codificados en UTF-8 como hace java.net.URLEncoder. Se imprime con nivel de corrección M, entre 30 y 40 mm, al principio de la primera página. Especificación vigente: versión 0.5.0, de 10 de diciembre de 2025.
URLs
| Modalidad | Entorno | URL base |
|---|---|---|
| VERI*FACTU | Producción | https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR |
| VERI*FACTU | Pruebas | https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR |
| Sin envío (NO VERI*FACTU) | Producción | https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu |
| Sin envío (NO VERI*FACTU) | Pruebas | https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu |
Parámetros
| Parámetro | Contenido | Formato | Longitud |
|---|---|---|---|
nif | NIF del obligado a expedir la factura | NIF | 9 |
numserie | Serie y número de la factura | ASCII 32 a 126 | Hasta 60 |
fecha | Fecha de expedición | DD-MM-AAAA | 10 |
importe | Importe total de la factura | Punto decimal; hasta 12 enteros y 2 decimales | — |
idioma (opcional) | Idioma de la respuesta | gl, ca, eu, es, va o en | 2 |
La codificación: el detalle que más falla
Los valores deben ir con «URL encoding» en UTF-8. La especificación lo ilustra con un número de serie que contiene «&»: 12345678&G33 debe escribirse 12345678%26G33. Sin codificar, la URL se parte y el cotejo falla.
El ejemplo oficial (apartado 4.1) está en Java y usa java.net.URLEncoder.encode(param, "UTF-8"). Esa función:
- deja sin codificar letras, dígitos y
. - * _; - convierte el espacio en
+; - codifica todo lo demás como
%XX, incluidos/,(,),!y~.
El encodeURIComponent de JavaScript difiere en cinco caracteres (! ' ( ) ~, que no codifica) y en el espacio (%20). Ambas formas se decodifican igual en un servidor estándar, pero si quieres un resultado idéntico al de la AEAT, usa estos fragmentos:
JavaScript
// Equivalente a java.net.URLEncoder.encode(valor, "UTF-8"), que usa el ejemplo oficial:
// deja sin codificar A-Z a-z 0-9 . - * _ y convierte el espacio en «+».
const javaUrlEncode = (v) =>
encodeURIComponent(v)
.replace(/[!'()~]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())
.replace(/%20/g, '+');
const BASE = {
produccion: 'https://www2.agenciatributaria.gob.es',
pruebas: 'https://prewww2.aeat.es',
};
export function urlQr({ nif, numserie, fecha, importe }, { verifactu = true, entorno = 'produccion' } = {}) {
const ruta = verifactu ? '/wlpl/TIKE-CONT/ValidarQR' : '/wlpl/TIKE-CONT/ValidarQRNoVerifactu';
return `${BASE[entorno]}${ruta}?nif=${javaUrlEncode(nif)}&numserie=${javaUrlEncode(numserie)}` +
`&fecha=${javaUrlEncode(fecha)}&importe=${javaUrlEncode(importe)}`;
}
console.log(urlQr({ nif: '89890001K', numserie: '12345678&G33', fecha: '01-01-2024', importe: '241.4' }, { entorno: 'pruebas' }));
Python
from urllib.parse import quote_plus
BASE = {
("verifactu", "produccion"): "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR",
("verifactu", "pruebas"): "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR",
("no_verifactu", "produccion"): "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu",
("no_verifactu", "pruebas"): "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu",
}
def url_qr(nif, numserie, fecha, importe, modo="verifactu", entorno="produccion"):
# quote_plus con safe="*" equivale a java.net.URLEncoder.encode(valor, "UTF-8"),
# que es lo que usa el ejemplo oficial: espacio -> "+", "&" -> "%26".
enc = lambda v: quote_plus(v, safe="*", encoding="utf-8")
return (
f"{BASE[(modo, entorno)]}?nif={enc(nif)}&numserie={enc(numserie)}"
f"&fecha={enc(fecha)}&importe={enc(importe)}"
)
if __name__ == "__main__":
# Ejemplo de la especificación del QR (v0.5.0): numserie con «&»
print(url_qr("89890001K", "12345678&G33", "01-01-2024", "241.4", entorno="pruebas"))
# https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=89890001K&numserie=12345678%26G33&fecha=01-01-2024&importe=241.4
Ambos se ejecutan en los tests de esta web y reproducen la URL de ejemplo de la especificación.
Requisitos de impresión
| Requisito | Valor |
|---|---|
| Norma | ISO/IEC 18004:2015 |
| Nivel de corrección | M |
| Tamaño | Entre 30×30 y 40×40 mm |
| Zona en blanco alrededor | Mínimo 2 mm; recomendado 6 mm |
| Posición | Al principio de la factura, solo en la primera página y como primer QR. Vertical: arriba, preferiblemente centrado. Apaisada: a la izquierda, cerca del margen superior |
| Texto encima | «QR tributario:» |
| Texto debajo (solo VERI*FACTU) | «Factura verificable en la sede electrónica de la AEAT» o «VERI*FACTU» |
| Tamaño de letra | Igual o mayor que el del resto de la factura |
Códigos de error del cotejo
| Código | Descripción de la AEAT |
|---|---|
| 1001–1004 | No se ha remitido el parámetro nif, numserie, fecha o importe |
| 2001 | El NIF tiene un formato erróneo o no es válido |
| 2002 | El número de serie excede el número máximo de caracteres |
| 2003 | El número de serie contiene caracteres no permitidos |
| 2004 | La fecha de expedición tiene formato inválido y debe tener el formato DD-MM-AAAA |
| 2005 | El importe tiene un formato incorrecto |
| 2006 | El importe excede el número máximo de caracteres |
| 3001 | Error técnico en los sistemas de la Agencia Tributaria |
| 3002 | Se ha excedido el número máximo de intentos permitidos por día |
El parámetro formato=json devuelve la respuesta del cotejo en JSON para consultas desde programas, pero nunca puede ir en la URL del QR.