Integrar VeriFactu en un TPV de hostelería: ejemplos de tickets, facturas y rectificativas

· Equipo Factuneo

Las peticiones completas de una mesa de restaurante: ticket F2 con IVA al 10 % y al 21 %, factura F3 que sustituye al ticket, rectificativas R5 por diferencias y por sustitución, anulación e impresión del QR. Verificadas contra la preproducción de la AEAT.

Lo que vamos a construir

Una mesa de restaurante de principio a fin, tal como la emitiría un TPV: el ticket, la factura que lo sustituye cuando el cliente la pide y las dos formas de rectificarlo. Estas peticiones las verificamos el 22 de octubre de 2026 contra la preproducción de la AEAT: los cuatro registros quedaron en estado Correcto y la consulta posterior confirmó la huella.

Si buscas el porqué antes que el cómo, empieza por VeriFactu en hostelería.

Lo básico

Qué Valor
Petición POST https://factuneo.es/api/v1/invoices
Entornos Misma URL: la clave decide (vsk_test_… va a la preproducción de la AEAT, vsk_live_… a producción)
Cabeceras x-api-key, Content-Type: application/json y, recomendada, Idempotency-Key (un valor único por ticket)
Fechas AAAA-MM-DD. La de expedición no puede ser futura
Importes Números JSON, no texto. En negativo en las rectificativas por diferencias
IVA desglose (obligatorio), una línea por tipo. lineas (opcional) lleva el precioUnitario sin IVA de cada concepto y debe cuadrar con el desglose tipo a tipo

1. El ticket (F2)

Mesa 7: dos menús y dos cafés al 10 % y una botella de vino para llevar al 21 %. Sin destinatarios: el ticket no identifica al cliente.

{
  "emisorNif": "B12345674",
  "numeroSerie": "T1-2026-000123",
  "fechaExpedicion": "2026-10-22",
  "tipoFactura": "F2",
  "nombreRazonEmisor": "RESTAURANTE EJEMPLO SL",
  "descripcionOperacion": "Consumo en mesa 7",
  "lineas": [
    { "descripcion": "Menú del día", "cantidad": 2, "precioUnitario": 15, "tipoImpositivo": 10 },
    { "descripcion": "Café", "cantidad": 2, "precioUnitario": 1.5, "tipoImpositivo": 10 },
    { "descripcion": "Botella de vino para llevar", "cantidad": 1, "precioUnitario": 12, "tipoImpositivo": 21 }
  ],
  "desglose": [
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 10, "baseImponible": 33, "cuotaRepercutida": 3.3 },
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 21, "baseImponible": 12, "cuotaRepercutida": 2.52 }
  ],
  "baseImponible": 45,
  "cuotaIva": 5.82,
  "importeTotal": 50.82
}
curl -X POST https://factuneo.es/api/v1/invoices \
  -H "x-api-key: vsk_test_..." \
  -H "Idempotency-Key: mesa7-20261022-1" \
  -H "Content-Type: application/json" \
  -d @ticket.json

La respuesta llega al momento, antes de que el registro se envíe a la AEAT:

{
  "invoiceId": "3f8a1c2e-…",
  "hash": "A5C114D6…",
  "qrUrl": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=T1-2026-000123&fecha=22-10-2026&importe=50.82",
  "qrString": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=T1-2026-000123&fecha=22-10-2026&importe=50.82"
}

qrString es el texto que se codifica en el QR del ticket. Guarda invoiceId: con él se consulta, se anula o se descarga el PDF.

Un límite a tener en cuenta: en hostelería la factura simplificada llega hasta 3.000 €, IVA incluido. Por encima, la AEAT rechaza el F2 (error 1150): hay que emitir una factura completa.

2. El cliente pide factura (F3)

Cuando el cliente pide factura de un ticket ya emitido, no se emite otra venta: se emite una F3 que sustituye al ticket, con sus datos y su domicilio en destinatarios y el ticket en facturasSustituidas.

{
  "emisorNif": "B12345674",
  "numeroSerie": "F-2026-000045",
  "fechaExpedicion": "2026-10-22",
  "tipoFactura": "F3",
  "nombreRazonEmisor": "RESTAURANTE EJEMPLO SL",
  "descripcionOperacion": "Consumo en mesa 7 (sustituye al ticket T1-2026-000123)",
  "facturasSustituidas": [{ "numeroSerie": "T1-2026-000123", "fechaExpedicion": "2026-10-22" }],
  "destinatarios": [{
    "nombreRazon": "CLIENTE EMPRESA SA",
    "nif": "A28000024",
    "domicilio": { "direccion": "Rúa do Vilar 10", "codigoPostal": "15705", "municipio": "Santiago de Compostela", "provincia": "A Coruña" }
  }],
  "lineas": [
    { "descripcion": "Menú del día", "cantidad": 2, "precioUnitario": 15, "tipoImpositivo": 10 },
    { "descripcion": "Café", "cantidad": 2, "precioUnitario": 1.5, "tipoImpositivo": 10 },
    { "descripcion": "Botella de vino para llevar", "cantidad": 1, "precioUnitario": 12, "tipoImpositivo": 21 }
  ],
  "desglose": [
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 10, "baseImponible": 33, "cuotaRepercutida": 3.3 },
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 21, "baseImponible": 12, "cuotaRepercutida": 2.52 }
  ],
  "baseImponible": 45,
  "cuotaIva": 5.82,
  "importeTotal": 50.82
}

El PDF de la factura, con los conceptos, los domicilios y el QR: GET /api/v1/invoices/{invoiceId}/pdf con la misma clave.

3. Devolver un café (R5 por diferencias)

Solo la variación, en negativo, y el ticket que se rectifica:

{
  "emisorNif": "B12345674",
  "numeroSerie": "R-2026-000007",
  "fechaExpedicion": "2026-10-22",
  "tipoFactura": "R5",
  "rectificativa": {
    "tipo": "I",
    "facturasRectificadas": [{ "numeroSerie": "T1-2026-000123", "fechaExpedicion": "2026-10-22" }],
    "motivo": "Devolución de un café no servido"
  },
  "nombreRazonEmisor": "RESTAURANTE EJEMPLO SL",
  "descripcionOperacion": "Devolución parcial del ticket T1-2026-000123",
  "lineas": [{ "descripcion": "Café", "cantidad": -1, "precioUnitario": 1.5, "tipoImpositivo": 10 }],
  "desglose": [
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 10, "baseImponible": -1.5, "cuotaRepercutida": -0.15 }
  ],
  "baseImponible": -1.5,
  "cuotaIva": -0.15,
  "importeTotal": -1.65
}

4. Rehacer la cuenta (R5 por sustitución)

Los importes correctos completos en el desglose y, en importeRectificacion, los del ticket original. Aquí se cobró un menú que no se sirvió:

{
  "emisorNif": "B12345674",
  "numeroSerie": "R-2026-000008",
  "fechaExpedicion": "2026-10-22",
  "tipoFactura": "R5",
  "rectificativa": {
    "tipo": "S",
    "facturasRectificadas": [{ "numeroSerie": "T1-2026-000123", "fechaExpedicion": "2026-10-22" }],
    "importeRectificacion": { "baseRectificada": 45, "cuotaRectificada": 5.82 },
    "motivo": "Se cobró un menú que no se sirvió"
  },
  "nombreRazonEmisor": "RESTAURANTE EJEMPLO SL",
  "descripcionOperacion": "Sustituye al ticket T1-2026-000123",
  "lineas": [
    { "descripcion": "Menú del día", "cantidad": 1, "precioUnitario": 15, "tipoImpositivo": 10 },
    { "descripcion": "Café", "cantidad": 2, "precioUnitario": 1.5, "tipoImpositivo": 10 },
    { "descripcion": "Botella de vino para llevar", "cantidad": 1, "precioUnitario": 12, "tipoImpositivo": 21 }
  ],
  "desglose": [
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 10, "baseImponible": 18, "cuotaRepercutida": 1.8 },
    { "claveRegimen": "01", "calificacionOperacion": "S1", "tipoImpositivo": 21, "baseImponible": 12, "cuotaRepercutida": 2.52 }
  ],
  "baseImponible": 30,
  "cuotaIva": 4.32,
  "importeTotal": 34.32
}

5. Un ticket que no debió existir: anular

Si el ticket se emitió por error —la mesa equivocada, una venta que no hubo—, no se rectifica: se anula su registro.

curl -X POST https://factuneo.es/api/v1/invoices/3f8a1c2e-…/anular \
  -H "x-api-key: vsk_test_..." -H "Content-Type: application/json" -d '{}'

Imprimir el QR en el ticket

Según la especificación del código QR de la AEAT:

En una impresora térmica de 80 mm cabe sin problema: codifica qrString tal cual, sin acortarlo.

Saber si la AEAT lo aceptó

El envío a la AEAT va por detrás, con reintentos si la AEAT no responde. El resultado te llega por webhook (invoice.accepted, invoice.rejected o invoice.error) o con GET /api/v1/invoices/{invoiceId}. Para el TPV, lo normal es imprimir con la respuesta inmediata y reflejar el estado en el backoffice.

Conexión y declaración responsable

Siguiente paso

Crea una cuenta (sin tarjeta), copia tu clave vsk_test_ y lanza el ticket de la mesa 7 contra la preproducción de la AEAT. Tienes todas las recetas y la referencia de campos en la documentación.

← Volver al blog · Factuneo