Integraciones

Conecta La Carta Viva con tu TPV

Cada pedido, cobro y reserva llega a tu TPV, a tu contabilidad o a tus automatizaciones en cuanto pasa, firmado para que nadie pueda colarse. Y si tu TPV prefiere preguntar, tiene una API para leer pedidos y marcar agotados. Abierto a cualquier proveedor: sin acuerdos y sin coste extra.

Para el restaurante: tres pasos

  1. En el panel, entra en Ajustes → Conexiones y pulsa «Nueva conexión».
  2. Pega la dirección que te dé tu proveedor (empieza por https://) y elige qué quieres que reciba: pedidos, cobros, reservas.
  3. Pásale el secreto que aparece y pulsa «Enviar prueba». Si contesta, ya está: desde ese momento le llega todo solo.

¿Usas Zapier, Make o n8n? Crea un disparador de tipo Webhook («Catch Hook» en Zapier), copia su dirección y pégala como conexión. Así puedes mandar cada reserva a una hoja de cálculo, cada pedido a tu contabilidad o un aviso a tu móvil, sin programar.

Eventos

Qué llega

Un POST con un JSON. Arriba, el sobre (igual en todos los eventos); dentro de datos, lo que ha pasado. Los importes van en céntimos y con el IVA incluido, como en la carta. Las fechas, en UTC (ISO 8601).

POST https://tu-tpv.com/webhooks/lacartaviva
Content-Type: application/json
X-LCV-Evento: pedido.creado
X-LCV-Envio: 0b7e5c1a-3f7d-4a52-9d1e-6c2f8a4b9e10
X-LCV-Firma: t=1791229330,v1=5f2c…e81a

{
  "id": "0b7e5c1a-3f7d-4a52-9d1e-6c2f8a4b9e10",
  "evento": "pedido.creado",
  "version": "2026-10-05",
  "creado": "2026-10-05T19:42:10.512Z",
  "local": { "id": "c1d2…", "nombre": "Casa Lola" },
  "datos": {
    "pedido": {
      "id": "9a8b…",
      "numero": 42,
      "estado": "placed",
      "canal": "mesa",
      "mesa": "7",
      "dia": "2026-10-05",
      "creado": "2026-10-05T19:42:10.498Z",
      "subtotal_cents": 2350,
      "impuestos_cents": 0,
      "propina_cents": 0,
      "total_cents": 2350,
      "lineas": [
        {
          "producto_id": "5e4f…",
          "nombre": "Croquetas de jamón",
          "cantidad": 2,
          "precio_unidad_cents": 850,
          "total_cents": 1700,
          "opciones": [],
          "notas": null,
          "es_envio": false
        },
        {
          "producto_id": "7c6d…",
          "nombre": "Caña",
          "cantidad": 1,
          "precio_unidad_cents": 650,
          "total_cents": 650,
          "opciones": [{ "nombre": "Grande", "precio_cents": 150 }],
          "notas": "Muy fría",
          "es_envio": false
        }
      ],
      "cliente": null
    }
  }
}

Notas sobre los pedidos

Cobros y reservas

Comprobar la firma

La cabecera X-LCV-Firma tiene la forma t=<segundos>,v1=<firma>. La firma es el HMAC-SHA256, en hexadecimal, de t + "." + cuerpo con el secreto de la conexión. Compárala siempre sobre el cuerpo tal cual llega y descarta lo que tenga más de cinco minutos.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';

// cuerpo: el texto TAL CUAL llega (sin volver a convertirlo a JSON).
export function firmaValida(cuerpo, cabecera, secreto) {
  const partes = Object.fromEntries(cabecera.split(',').map((p) => p.split('=')));
  const t = Number(partes.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // más de 5 min: se descarta
  const esperada = createHmac('sha256', secreto).update(`${t}.${cuerpo}`).digest('hex');
  const recibida = Buffer.from(partes.v1 ?? '', 'hex');
  return recibida.length === 32 && timingSafeEqual(recibida, Buffer.from(esperada, 'hex'));
}

Python

import hashlib, hmac, time

def firma_valida(cuerpo: bytes, cabecera: str, secreto: str) -> bool:
    partes = dict(p.split("=", 1) for p in cabecera.split(","))
    t = int(partes.get("t", "0"))
    if abs(time.time() - t) > 300:  # más de 5 min: se descarta
        return False
    esperada = hmac.new(secreto.encode(), f"{t}.".encode() + cuerpo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada, partes.get("v1", ""))

PHP

function firma_valida(string $cuerpo, string $cabecera, string $secreto): bool {
    parse_str(str_replace(',', '&', $cabecera), $p);
    $t = (int) ($p['t'] ?? 0);
    if (abs(time() - $t) > 300) return false; // más de 5 min: se descarta
    $esperada = hash_hmac('sha256', $t . '.' . $cuerpo, $secreto);
    return hash_equals($esperada, $p['v1'] ?? '');
}

Si tu TPV pregunta: la API

Para los TPV que no pueden recibir avisos (un ordenador dentro del local, detrás del router). El restaurante crea una clave de API en Ajustes → Conexiones (se enseña una sola vez) y elige sus permisos: leer y agotados. Va en la cabecera Authorization: Bearer lcv_… de cada petición.

GET https://panel.lacartaviva.com/api/v1/pedidos?desde=2026-10-05T18:00:00Z
Authorization: Bearer lcv_…

{
  "datos": [ { "id": "9a8b…", "numero": 42, "estado": "placed", "canal": "mesa", "lineas": [ … ] } ],
  "siguiente": "2026-10-05T19:42:10.498Z",
  "hay_mas": false
}
PATCH https://panel.lacartaviva.com/api/v1/platos/5e4f…
Authorization: Bearer lcv_…
Content-Type: application/json

{ "disponible": false }

Entregas y reintentos

¿Eres proveedor de TPV o de software para hostelería?

Escríbenos a hola@lacartaviva.com y te damos un local de pruebas para montar la integración con datos de verdad.

Preguntas frecuentes

¿Funciona con mi TPV?

Funciona con cualquier TPV, programa de contabilidad o automatización que pueda recibir avisos por internet (lo que los técnicos llaman «webhook»). Muchos ya lo hacen; si el tuyo no, Zapier, Make o n8n hacen de puente. Pásale esta página a tu proveedor: con ella tiene todo lo que necesita.

¿Cuesta algo conectarlo?

No. Las conexiones van incluidas en la cuota, sin acuerdos ni permisos: el restaurante las da de alta en Ajustes y su proveedor las recibe. Hasta 10 por local.

¿Y si mi TPV está apagado o no contesta?

Se reintenta solo: al minuto, a los 5 minutos, a la media hora, a las 2 horas y a las 12 horas. En el panel se ve cada envío, si llegó y qué contestó el otro lado.

¿Mi TPV es un ordenador del local, sin dirección en internet. ¿Sirve?

Sí. En vez de esperar avisos, el TPV pregunta él cada pocos segundos con una clave de API: pedidos, cobros y reservas nuevos. Y con la misma clave puede leer la carta y marcar un plato agotado, que desaparece de todas las mesas al momento.