TPV y punto de venta

TicketBAI en tu TPV, sin rehacer la caja

Tickets y facturas simplificadas con el QR listo para imprimir, un envío que no tiene por qué frenar la caja y los fallos bajo control. Esto es lo que tienes que saber para integrarlo.

Qué cambia en un TPV con TicketBAI

Cada ticket que emite tu TPV es una factura simplificada, y con TicketBAI cada una genera un registro: se firma, se encadena con el anterior y se envía a la hacienda foral que corresponda al comercio. Tu caja sigue calculando importes, impuestos y numeración como hasta ahora. Lo nuevo es ese registro y lo que tiene que acompañar al ticket.

Con KubiBAI, tu TPV manda la venta en JSON y recibe el registro ya generado, su estado, el identificador TBAI y el QR. No construyes XML ni gestionas la firma de la factura: KubiBAI firma con su certificado de Software Garante, o con el tuyo si eres titular de uno. Si el TPV tiene su propio certificado dado de alta en el panel, la petición lo indica (más abajo).

Lo que tiene que llevar el ticket

El ticket, impreso o digital, lleva el QR de TicketBAI y el identificador de la factura. En la respuesta recibes:

  • tbai_identifier: el código único de la factura en TicketBAI.
  • batuz_qr_value y batuz_qr_image: la URL que codifica el QR y la imagen, para imprimirla tal cual.
  • signature_value: la firma, que sirve para encadenar la factura siguiente.

Tu TPV solo tiene que pintarlos en el ticket.

No hace falta esperar a la hacienda para imprimir

En una caja, medio segundo importa. Por eso el envío tiene tres modos, que eliges en cada petición con async_mode:

Modo Firma Envío a la hacienda Qué devuelve
none Síncrona Síncrono La factura ya procesada, con el resultado de la hacienda.
lite Síncrona Asíncrono El estado inicial, con el identificador TBAI y el QR ya generados.
full Asíncrona Asíncrono La confirmación de que la factura está en cola.

Para un TPV, lite suele ser el punto medio: tienes el QR al instante para imprimir el ticket, y el envío a la hacienda se hace aparte. Eso sí, la respuesta inicial no dice que la hacienda haya aceptado la factura: si luego la rechaza, el ticket ya entregado lleva el QR de una factura rechazada, y habrá que corregirla y reenviarla. El resultado final te llega por webhook a la URL que indiques en webhook_url, o lo consultas cuando quieras con el endpoint de estado.

Si el comercio emite pocas ventas y prefieres lo más simple, none te da el resultado de la hacienda en la propia respuesta.

Del ticket a la factura completa

Un ticket se envía con is_simplified_invoice: true y sin destinatario. Si el cliente pide después una factura con sus datos, se emite una factura completa que indica cuál sustituye: issued_in_substitution_of_simplified: true y la simplificada en replaced_rectified_invoices.

No es una rectificativa y no usa sus campos. Los dos casos, con su JSON, están en los ejemplos de la API.

La cadena y las varias cajas

TicketBAI encadena las facturas: cada una lleva los datos de la anterior. Tienes dos formas de resolverlo:

  • Envías previous_invoice_data en cada factura, con la firma de la anterior. Si KubiBAI reconoce el signature_value, completa la serie, el número y la fecha.
  • Si has activado el encadenamiento automático para la empresa o para el TPV, puedes omitirlo. Es opcional y se activa desde el panel web de KubiBAI.

Cuando una factura sale de un TPV dado de alta en el panel de KubiBAI, la petición incluye pos_system_data con el identificador que el panel asigna a ese TPV y la contraseña del certificado del TPV con el que se firma la factura; KubiBAI no la guarda. Así KubiBAI sabe de qué TPV viene cada factura y puede encadenarlas por TPV. Si necesitas saber cuál fue la última factura correcta o cuáles están a ambos lados de otra, hay endpoints para la última factura y para las facturas adyacentes.

Si algo falla

  • La hacienda rechaza la factura. tbai_post_status llega como fail y tbai_post_response_data trae la respuesta de la hacienda. Si el rechazo es por el contenido, reenviar no lo arreglará: corrige los datos.
  • No hay respuesta o falla la conexión. Si la respuesta es indeterminada, la factura queda con has_undefined_response: true. El endpoint de reenvío reintenta el envío con el mismo XML firmado, sin regenerarlo ni volver a firmar, así que conserva las fechas de emisión y de firma.
  • La hacienda está en mantenimiento. KubiBAI responde 503 con Retry-After, los segundos hasta el final previsto.
  • Demasiadas peticiones. Responde 429, también con Retry-After.

Los errores de validación y de negocio llegan siempre con la misma forma, bajo data.error, con code, message, http_code y, cuando procede, los errores campo a campo.

Anular y corregir

Anular una factura es un registro adicional de TicketBAI: no la borra, la invalida a efectos fiscales, y su número no se puede reutilizar. Para corregir datos sin cambiar el importe total, en Araba y Gipuzkoa existe Zuzendu; en Bizkaia no. Si cambia el importe, toca una factura rectificativa, por sustitución o por diferencias.

Cómo se integra

  1. Pide acceso al entorno de pruebas y da de alta una empresa con su territorio.
  2. Envía un ticket de prueba. Las facturas de pruebas se remiten a los servidores de pruebas de TicketBAI, así que validas el flujo completo con la hacienda de verdad.
  3. Imprime el QR y el identificador en el ticket.
  4. Prueba los fallos: un rechazo, un reenvío y una anulación.
  5. Pasa a producción cambiando la URL base y las credenciales.

Si tus clientes también facturan fuera de Euskadi, el mismo planteamiento para la AEAT está en VeriFactu para TPV, con KubiFACTU.

Ejemplos

Las peticiones de un TPV

El ticket y la factura completa que lo sustituye, tal y como los usarías en una caja.

Factura simplificada

Una venta en mostrador de 60,50 € sin identificar al destinatario.

  • is_simplified_invoice: true permite omitir recipients; no mandes un array vacío ni datos ficticios.
  • No es el régimen simplificado de IVA: no actives is_equalization_or_simplified_operation por ser una simplificada.
  • En Gipuzkoa, concepts es obligatorio también en las simplificadas.
Ver este ejemplo en la documentación (abre en nueva pestaña)
POST /invoicing/invoices
{  "sender_data": {    "sender_ticketbai_territory_id": "dded8ad1-0574-4ee4-b4f7-36687cba73b9",    "sender_is_individual": false,    "sender_full_name": "Empresa Ejemplo S.L.",    "sender_id_card_number": "B12345674"  },  "invoice_data": {    "fiscal_year": 2026,    "series_code": "A",    "invoice_number": "2026-004",    "invoice_datetime": "2026-07-22 11:30:00",    "is_simplified_invoice": true,    "description": "Venta en mostrador",    "total_amount": 6050,    "vat_keys": [      {        "transaction_of_tax_significance_code": "01"      }    ],    "concepts": [      {        "description": "Venta en mostrador",        "quantity": 100,        "unit_price": 5000000000,        "total_price": 6050      }    ],    "local_subject_not_exempt_items": [      {        "vat_not_exempt_type": "S1",        "vat_breakdown_items": [          {            "taxable_amount": 5000,            "tax_rate": 2100,            "tax_quota": 1050          }        ]      }    ]  },  "previous_invoice_data": {    "signature_value": "firma-ficticia-factura-2026-003",    "series_code": "A",    "invoice_number": "2026-003",    "invoice_date": "2026-07-22"  }}

Sustitución de factura simplificada

La factura completa que se emite cuando el cliente de un ticket pide una factura con sus datos.

  • issued_in_substitution_of_simplified: true indica que sustituye a una simplificada, e replaced_rectified_invoices dice cuál.
  • Es una factura completa, así que lleva recipients.
  • No es una rectificativa: no incluyas rectificative_reason_code ni los campos rectified_*.
Ver este ejemplo en la documentación (abre en nueva pestaña)
POST /invoicing/invoices
{  "sender_data": {    "sender_ticketbai_territory_id": "dded8ad1-0574-4ee4-b4f7-36687cba73b9",    "sender_is_individual": false,    "sender_full_name": "Empresa Ejemplo S.L.",    "sender_id_card_number": "B12345674"  },  "recipients": [    {      "tax_id_number": "B76543214",      "full_name": "Cliente Ejemplo S.A.",      "postal_code": "20004",      "address": "Avenida de la Libertad 1"    }  ],  "invoice_data": {    "fiscal_year": 2026,    "series_code": "A",    "invoice_number": "2026-005",    "invoice_datetime": "2026-07-22 12:00:00",    "issued_in_substitution_of_simplified": true,    "replaced_rectified_invoices": [      {        "series_code": "A",        "invoice_number": "2026-004",        "invoice_date": "2026-07-22"      }    ],    "description": "Venta en mostrador",    "total_amount": 6050,    "vat_keys": [      {        "transaction_of_tax_significance_code": "01"      }    ],    "concepts": [      {        "description": "Venta en mostrador",        "quantity": 100,        "unit_price": 5000000000,        "total_price": 6050      }    ],    "local_subject_not_exempt_items": [      {        "vat_not_exempt_type": "S1",        "vat_breakdown_items": [          {            "taxable_amount": 5000,            "tax_rate": 2100,            "tax_quota": 1050          }        ]      }    ]  },  "previous_invoice_data": {    "signature_value": "firma-ficticia-factura-2026-004",    "series_code": "A",    "invoice_number": "2026-004",    "invoice_date": "2026-07-22"  }}

Dudas habituales

Preguntas sobre TicketBAI en el TPV

¿Tengo que esperar a la hacienda para imprimir el ticket?

No necesariamente. Con async_mode «lite» KubiBAI firma en el momento y te devuelve el identificador TBAI y el QR para imprimir, y el envío a la hacienda se hace aparte. Con «none» recibes el resultado de la hacienda en la propia respuesta, y con «full» tanto la firma como el envío son asíncronos.

¿Qué tiene que llevar el ticket con TicketBAI?

El QR de TicketBAI y el identificador de la factura. KubiBAI te devuelve el identificador TBAI, la URL del QR y su imagen, así que tu TPV solo tiene que imprimirlos.

¿Qué pasa si falla la conexión o la hacienda no responde?

Si la respuesta queda indeterminada, la factura se marca con has_undefined_response y puedes reenviarla: el reenvío repite el envío con el mismo XML firmado, sin regenerarlo, y conserva las fechas de emisión y de firma. Si la hacienda está en mantenimiento, KubiBAI responde 503 con Retry-After.

¿Cómo se encadenan los tickets de varias cajas?

Cada factura lleva los datos de la anterior de su cadena en previous_invoice_data, o, si has activado el encadenamiento automático (opcional, desde el panel web) para la empresa o el TPV, puedes omitirlo. Las facturas que salen de un TPV dado de alta en el panel incluyen pos_system_data, con el identificador del TPV y la contraseña de su certificado.

¿Puedo emitir la factura completa después del ticket?

Sí. El ticket es una factura simplificada y la factura completa se emite en sustitución de la simplificada, indicando cuál sustituye. No es una rectificativa.

¿Se puede anular un ticket?

Sí. La anulación es un registro adicional de TicketBAI: no elimina la factura original, pero la invalida a efectos fiscales, y su número no puede reutilizarse.

Empieza hoy

Conecta tu software con TicketBAI

Pídenos acceso al entorno de pruebas, integra con la documentación y pasa a producción cuando todo encaje.