Cómo usar la API de Verifactu: enviar, subsanar, anular y obtener el QR

El plugin Verifactu añade a la API de FacturaScripts el endpoint verifactu. Con él puedes enviar facturas de venta a la AEAT, subsanarlas, anularlas y obtener su código QR desde otra aplicación, como tu tienda online, un TPV o un ERP. En esta guía tienes los requisitos, los parámetros, ejemplos con curl y PHP, los errores posibles y cómo saber si Hacienda ha aceptado cada factura.

Antes de empezar

  • Plugin configurado: la empresa debe tener el certificado cargado y el ejercicio en modo Verifactu o NO Verifactu. Lo explicamos en cómo configurar Verifactu.
  • Cron activo: la API no envía nada a la AEAT por sí misma. Prepara el registro de facturación y el cron de Verifactu lo firma y lo envía. Debe ejecutarse cada 60 segundos.
  • API activada y una clave: sigue la guía de la API REST de FacturaScripts. Todas las peticiones llevan la cabecera Token con tu clave.
  • Permisos de la clave: si la clave no tiene acceso completo, dale acceso al recurso verifactu: POST para enviar, subsanar y anular, y GET para el QR. Para consultar el resultado necesitas además GET en facturaclientes y verifacturegistrofacturas.
  • Haz las pruebas en modo de pruebas: mientras la empresa tenga activado el modo de pruebas, los registros van al entorno de pruebas de la AEAT y no a Hacienda.

El endpoint verifactu

La URL es https://tu-dominio/api/3/verifactu. Siempre recibe dos parámetros, que van en la URL (query string), también en las peticiones POST:

  • idfactura: el identificador interno de la factura de venta, no su código.
  • action: la operación que quieres hacer.
actionMétodoQué haceCuándo se puede usar
altaPOSTCrea el registro de alta de la factura y lo deja en cola para enviarloLa factura no se ha enviado, no está anulada y no tiene un alta pendiente
subsanacionPOSTVuelve a enviar los datos del cliente de una factura ya enviadaLa AEAT ya ha aceptado el alta
anulacionPOSTAnula la factura en la AEATLa AEAT ya ha aceptado el alta
qrGETDevuelve la imagen del QR tributarioLa factura tiene registros y no está anulada

Enviar una factura (alta)

Hay dos formas de hacerlo.

Opción 1: al cambiar el estado de la factura

Marca la opción Enviar a Verifactu en un estado de factura de cliente, como se explica en cómo enviar una factura a Verifactu. Después:

  1. Crea la factura con el endpoint crearFacturaCliente. La respuesta incluye el idfactura.
  2. Actualiza la factura con un PUT a /api/3/facturaclientes/{idfactura} y pon en idestado el estado que hace el envío.

Es la opción más cómoda si ya trabajas con estados: la factura se envía igual desde la API que desde el programa.

Opción 2: con la acción alta

Indica tú qué factura quieres enviar:

curl -X POST "https://tu-dominio/api/3/verifactu?idfactura=123&action=alta" \
  -H "Token: TU_CLAVE"

Respuesta:

{"status":"success","message":"Invoice sent to Verifactu successfully"}

Importante: esta respuesta significa que el registro se ha creado y está en la cola, no que la AEAT lo haya aceptado. El cron lo enviará en el siguiente minuto. Para conocer el resultado, consulta el estado del registro como se explica a continuación.

Comprobar si la AEAT ha aceptado la factura

Forma rápida

Consulta la factura con un GET a /api/3/facturaclientes/123. El campo vf_sent pasa a true cuando la AEAT acepta el alta, aunque sea con errores.

Con el detalle de cada registro

Cada alta, subsanación o anulación crea un registro de facturación. Puedes listarlos con un GET al recurso verifacturegistrofacturas, filtrando por factura:

curl "https://tu-dominio/api/3/verifacturegistrofacturas?filter[idfactura]=123&sort[id]=DESC" \
  -H "Token: TU_CLAVE"

Los campos más útiles son:

  • event: Alta, Subsanacion o Anulacion.
  • mode: verifactu o no-verifactu.
  • status: el resultado.
  • csv y timestamp_presentacion: el código seguro de verificación y la fecha y hora en que la AEAT recibió el registro.
  • response: la respuesta de la AEAT. Aquí verás el motivo si la ha rechazado.
statusSignificado
(vacío)Pendiente: el cron todavía no lo ha procesado
CorrectoAceptado por la AEAT
AceptadoConErroresAceptado, pero la AEAT avisa de algún dato a revisar
IncorrectoRechazado por la AEAT. Revisa response
ErrorHash o ErrorSignatureError al calcular la huella o la firma antes del envío. Revisa los logs

En modo NO Verifactu los registros no se envían a la AEAT: se firman y se guardan en tu instalación por si te los pide en un requerimiento.

Si consultas el estado de forma periódica, hazlo cada minuto o más: el cron no procesa los registros con más frecuencia. Tienes más formas de filtrar en listar, filtrar y ordenar registros desde la API.

Subsanar una factura

La subsanación solo corrige los datos del cliente de una factura ya aceptada: CIF/NIF, nombre o razón social, dirección, apartado, código postal, ciudad, provincia y país. Los importes, las líneas y la fecha no se pueden cambiar.

  1. Actualiza los datos del cliente en la factura con un PUT a /api/3/facturaclientes/{idfactura}. Los campos son cifnif, nombrecliente, direccion, apartado, codpostal, ciudad, provincia y codpais.
  2. Llama a la acción subsanacion:
curl -X POST "https://tu-dominio/api/3/verifactu?idfactura=123&action=subsanacion" \
  -H "Token: TU_CLAVE"

Puedes subsanar una factura todas las veces que necesites. Más información en cómo subsanar una factura en Verifactu.

Anular una factura

curl -X POST "https://tu-dominio/api/3/verifactu?idfactura=123&action=anulacion" \
  -H "Token: TU_CLAVE"

La anulación no tiene vuelta atrás y el QR de la factura deja de valer. Si te equivocaste en los importes o en las líneas, lo normal es hacer una factura rectificativa con el endpoint crearFacturaRectificativaCliente y enviarla como cualquier otra factura. Anula solo las facturas que ya no son válidas. Lo explicamos en cómo anular una factura en Verifactu.

Obtener el QR

curl "https://tu-dominio/api/3/verifactu?idfactura=123&action=qr" \
  -H "Token: TU_CLAVE"

La respuesta no es JSON: es directamente la imagen PNG codificada en base64, con este formato:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...

Puedes usarla tal cual en el atributo src de una etiqueta <img> o decodificarla para guardar el PNG. El QR está disponible en cuanto existe el registro de alta, aunque el cron todavía no lo haya enviado. Si la factura está anulada, la API devuelve un error.

Respuestas de error

Los errores responden con un JSON como este:

{"status":"error","message":"Invoice not found"}
Código HTTPmessageCausa
400Invalid actionEl parámetro action falta o no es alta, subsanacion, anulacion ni qr
405Method not allowed for QR actionHas pedido el QR con un método distinto de GET
405Method not allowed for this actionHas pedido alta, subsanación o anulación con un método distinto de POST
404Invoice not foundNo existe ninguna factura de venta con ese idfactura
400Invoice already sent to VerifactuLa AEAT ya aceptó el alta de esa factura
400Invoice not sent to VerifactuHas intentado subsanar o anular una factura cuya alta no ha aceptado la AEAT
400Invoice has no records in VerifactuHas pedido el QR de una factura sin registros
400Invoice is cancelled in VerifactuHas pedido el QR de una factura anulada
500Error sending invoice to VerifactuNo se pudo crear el registro de alta. Por ejemplo, ya hay un alta pendiente de enviar, la factura está anulada o la empresa no está configurada
500Error correcting invoice in VerifactuNo se pudo crear la subsanación. Por ejemplo, ya hay otra pendiente
500Error cancelling invoice in VerifactuNo se pudo crear la anulación. Por ejemplo, ya hay otra pendiente

Ante un error 500, mira el motivo en Informes → Verifactu, pestaña Logs, o en Administrador → Logs. Si el error es de la clave o de la versión de la API, consulta la guía de la API REST de FacturaScripts.

Ejemplo completo en PHP

Este ejemplo envía una factura existente, espera a que la AEAT responda y guarda el QR:

<?php
$url = 'https://tu-dominio/api/3/';
$token = 'TU_CLAVE';
$idfactura = 123;

function api(string $method, string $endpoint): array
{
    global $url, $token;
    $ch = curl_init($url . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => ['Token: ' . $token],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $body = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    return [$code, $body];
}

// 1. dejamos la factura en la cola de envío
[$code, $body] = api('POST', 'verifactu?idfactura=' . $idfactura . '&action=alta');
if ($code !== 200) {
    exit('Error ' . $code . ': ' . $body . PHP_EOL);
}

// 2. esperamos a que el cron la envíe (máximo 10 minutos)
for ($i = 0; $i < 10; $i++) {
    sleep(60);
    [, $body] = api('GET', 'verifacturegistrofacturas?filter[idfactura]=' . $idfactura
        . '&filter[event]=Alta&sort[id]=DESC&limit=1');
    $status = json_decode($body, true)[0]['status'] ?? '';
    if ($status !== '') {
        echo 'Estado: ' . $status . PHP_EOL;
        break;
    }
}

// 3. guardamos el QR en un PNG
[$code, $qr] = api('GET', 'verifactu?idfactura=' . $idfactura . '&action=qr');
if ($code === 200) {
    file_put_contents('qr-' . $idfactura . '.png', base64_decode(explode(',', $qr, 2)[1]));
}
Cookies
Usamos cookies necesarias para el funcionamiento del sitio y cookies opcionales para recordar tus preferencias y mejorar tu experiencia. Puedes aceptarlas todas, rechazarlas o configurar tus preferencias

Soporte