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

ModalidadEntornoURL base
VERI*FACTUProducciónhttps://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR
VERI*FACTUPruebashttps://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR
Sin envío (NO VERI*FACTU)Producciónhttps://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu
Sin envío (NO VERI*FACTU)Pruebashttps://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu

Parámetros

ParámetroContenidoFormatoLongitud
nifNIF del obligado a expedir la facturaNIF9
numserieSerie y número de la facturaASCII 32 a 126Hasta 60
fechaFecha de expediciónDD-MM-AAAA10
importeImporte total de la facturaPunto decimal; hasta 12 enteros y 2 decimales—
idioma (opcional)Idioma de la respuestagl, ca, eu, es, va o en2

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

RequisitoValor
NormaISO/IEC 18004:2015
Nivel de correcciónM
TamañoEntre 30×30 y 40×40 mm
Zona en blanco alrededorMínimo 2 mm; recomendado 6 mm
PosiciónAl 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 letraIgual o mayor que el del resto de la factura

Códigos de error del cotejo

CódigoDescripción de la AEAT
1001–1004No se ha remitido el parámetro nif, numserie, fecha o importe
2001El NIF tiene un formato erróneo o no es válido
2002El número de serie excede el número máximo de caracteres
2003El número de serie contiene caracteres no permitidos
2004La fecha de expedición tiene formato inválido y debe tener el formato DD-MM-AAAA
2005El importe tiene un formato incorrecto
2006El importe excede el número máximo de caracteres
3001Error técnico en los sistemas de la Agencia Tributaria
3002Se 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.

Preguntas frecuentes

¿Qué nivel de corrección de errores lleva el QR?

Nivel M (medio), según la especificación del QR. Versiones anteriores de los ejemplos usaban el nivel L; la versión 0.4.1 los cambió a M.

¿Cómo se codifica un espacio en la URL del QR?

El ejemplo oficial usa java.net.URLEncoder.encode(valor, "UTF-8"), que convierte el espacio en «+». En JavaScript, encodeURIComponent lo convierte en %20; nuestro fragmento lo ajusta para que el resultado sea idéntico al de Java.

¿Puedo añadir formato=json a la URL del QR?

No. La especificación dice que el parámetro formato nunca puede incorporarse en la URL que va en el código QR. Solo sirve para consultar el servicio de cotejo desde un programa.

¿Cuántas veces se puede consultar el servicio de cotejo?

La especificación prevé el error 3002 cuando se excede el número máximo de intentos permitidos por día, pero no publica el límite.

Fuentes oficiales

Revisado el 25 de septiembre de 2026
  1. AEAT: Especificaciones técnicas del código QR y del servicio de cotejo (v0.5.0, 10/12/2025)AEAT
  2. AEAT: Documentación técnica de VERI*FACTU para desarrolladoresAEAT
  3. Orden HAC/1177/2024, de 17 de octubre (especificaciones técnicas)BOE

Cada dato de esta página se ha contrastado con estas fuentes el . Si la norma cambia, la actualizamos y cambia esta fecha.