Integra Menumetrik con tu POS / ERP
Esta guía es para el área técnica del restaurante. Menumetrik empuja cada comanda pagada a tu sistema en tiempo real (webhook firmado) y expone una API de consulta para conciliar. El contrato lo define Menumetrik y está versionado — tu integración no se rompe sin aviso.
Cómo funciona
1 · Webhook (push)
El dueño registra la URL https de tu servidor en su portal (sección Integra tu sistema). Cada vez que una comanda queda pagada, Menumetrik hace un POST JSON firmado a esa URL con el detalle completo: mesa, líneas, totales, pagos por método y propina.
2 · API de consulta (pull)
Con la API key del restaurante puedes pedir las comandas pagadas desde una fecha. Úsala para reconciliar si tu servidor estuvo caído — devuelve exactamente el mismo formato que el webhook.
Las credenciales (signing secret y API key) las genera el dueño de la cuenta en menumetrik.com → Portal → Integra tu sistema. Se muestran una sola vez — pídeselas por un canal seguro.
El webhook, a detalle
- Método: POST · Content-Type: application/json
- Evento principal: check.paid (comanda pagada por completo). También ping (prueba desde el portal).
- Headers: X-Mmk-Event (tipo), X-Mmk-Event-Id (único), X-Mmk-Signature (firma).
- Respuesta esperada: cualquier 2xx en menos de 10 s. Responde rápido y procesa en background.
- Reintentos: 3 intentos (inmediato, +30 s, +5 min) si no hay 2xx. Después queda registrado como fallido — recupéralo con la API de consulta.
- Idempotencia: deduplica por X-Mmk-Event-Id (o el campo id del body): un reintento puede llegar duplicado si tu 2xx se perdió en la red.
- Requisitos de la URL: https público (nada de http, localhost ni IPs privadas).
Verificar la firma (obligatorio)
Cada POST lleva X-Mmk-Signature: t=<unix>,v1=<hex> donde v1 = HMAC_SHA256(secret, `{t}.{cuerpo_crudo}`). Valida la firma con comparación de tiempo constante y rechaza si t tiene más de 5 minutos (anti-replay).
Node.js / Express
const crypto = require("crypto");
function verifyMmk(req, secret) {
const sig = req.get("X-Mmk-Signature") || ""; // "t=1722690000,v1=ab12..."
const t = sig.match(/t=(\d+)/)?.[1];
const v1 = sig.match(/v1=([a-f0-9]+)/)?.[1];
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min
const expected = crypto.createHmac("sha256", secret)
.update(`${t}.${req.rawBody}`) // cuerpo CRUDO, sin re-serializar
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Python / Flask · FastAPI
import hmac, hashlib, time
def verify_mmk(signature_header: str, raw_body: bytes, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in signature_header.split(","))
t, v1 = parts.get("t"), parts.get("v1")
if not t or not v1 or abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(secret.encode(),
f"{t}.".encode() + raw_body, # cuerpo CRUDO
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)⚠️ Firma sobre el cuerpo crudo del request. Si tu framework parsea el JSON y lo re-serializa, la firma no va a coincidir — usa el raw body.
El payload (check.paid)
IDs como string, montos con 2 decimales en MXN, fechas en hora de CDMX (America/Mexico_City). El campo api_version te garantiza que el shape no cambia sin aviso.
{
"id": "evt_ab12cd34ef56...", // único por entrega — deduplica con esto
"type": "check.paid",
"api_version": "2026-08-03",
"created_at": "2026-08-03 14:22:10",
"timezone": "America/Mexico_City",
"currency": "MXN",
"restaurant_id": "4503599627370497",
"data": {
"check": {
"id": "12",
"restaurant_id": "4503599627370497",
"table_id": "3",
"table_label": "Mesa 4 + 5", // mesas combinadas incluidas
"waiter_staff_id": "7",
"managed_by": "both", // waiter | diner | both
"status": "paid",
"opened_at": "2026-08-03 13:01:44",
"closed_at": "2026-08-03 14:22:08",
"members": [ // comensales de la mesa
{ "member_id": "21", "diner_id": "9", "display_name": "Juan",
"group_id": "1", "status": "active" }
],
"groups": [ // cuentas separadas dentro de la mesa
{ "id": "1", "name": "Cuenta de Juan", "total_list": 190.00,
"total_qtapp": 184.30, "paid_total": 190.00, "pending_total_list": 0.00 }
],
"orders": [ // líneas con precio CONGELADO al ordenar
{ "id": "55", "name": "Tacos al pastor", "qty": 2,
"unit_price_list": 95.00, "unit_price_qtapp": 92.15,
"line_total_list": 190.00, "line_total_qtapp": 184.30,
"member_id": "21", "member_name": "Juan", "group_id": "1",
"item_id": "14", "package_id": null, "station_id": "2",
"notes": null, "status": "delivered", "created_by": "diner" }
],
"totals": {
"total_list": 190.00, // total a precio de menú
"total_qtapp": 184.30, // total si todo se paga con QTApp
"qtapp_savings": 5.70,
"paid_total": 190.00, // ya cubierto (equivalente lista)
"pending_total_list": 0.00
},
"payments": [ // SOLO completados
{ "id": "31", "method": "qtapp", // cash | card | qtapp
"amount": 184.30, "tip_amount": 27.65,
"status": "completed", "group_id": "1", "member_id": null,
"external_ref": "MMK-31",
"qtapp_transaction_id": "8837...", // referencia del pago real
"completed_at": "2026-08-03 14:22:08" }
]
}
}
}Notas para tu contabilidad
- payments[].method: cash/card se cobraron con la terminal del restaurante (Menumetrik solo tipifica); qtapp son operaciones liquidadas al restaurante a través de la infraestructura de QTApp y las instituciones financieras autorizadas que la respaldan.
- La propina (tip_amount) nunca lleva el descuento QTApp y viaja aparte del consumo.
- Los pagos qtapp traen qtapp_transaction_id para cruzar contra el estado de cuenta QTApp.
- Una mesa puede pagar mixto (unos en efectivo, otros por QTApp, por grupo o por la mesa completa).
API de consulta (conciliación)
curl "https://backend.tkm.technology/api/menumetrik/integration/checks?since=2026-08-01&limit=50" \
-H "X-Mmk-Api-Key: mmk_live_..."
# → { "api_version": "2026-08-03", "count": 2, "checks": [ { ...mismo shape que data.check... } ] }- since: YYYY-MM-DD o YYYY-MM-DD HH:MM:SS (hora CDMX). limit: 1–200 (default 50).
- Solo comandas con status=paid, ordenadas por cierre. Pagina moviendo since al último closed_at recibido.
- La key es por restaurante/sucursal: solo ve sus propias comandas.
Buenas prácticas y seguridad
- Verifica la firma siempre — es tu única garantía de que el evento viene de Menumetrik.
- Responde 2xx de inmediato y encola el procesamiento; timeouts cuentan como fallo y disparan reintento.
- Deduplica por id del evento antes de escribir en tu sistema.
- Guarda secret y API key en tu gestor de secretos, nunca en el código. Si se comprometen, el dueño puede rotar ambos desde su portal al instante.
- Corre una conciliación diaria con la API de consulta (por ejemplo, el corte del día anterior) — los webhooks son en tiempo real, el pull es tu red de seguridad.
¿Aún no usas Menumetrik en tu restaurante?
Es gratis — solo necesitas tu Cuenta Platino en QTApp.
Conoce Menumetrik para restaurantes