Saltar al contenido
Volver a documentación

Integración de molinetes y hardware

Beepers permite integrar molinetes y lectores QR físicos en la entrada de tu local. Cada dispositivo se conecta vía WebSocket y valida entradas en tiempo real contra el sistema.

Contrato técnico: turnstile-integration-v1

1. Registrar un dispositivo

Recomendado: crear y configurar todo desde dashboard. Al crear un dispositivo obtenés la API key y el paquete de integración sin usar cURL.

Flujo recomendado desde dashboarddashboard
Dashboard > Organizacion > Settings > Molinetes

1) Crear dispositivo y guardar API key
2) Elegir evento para integracion
3) Configurar reglas: todas o tipos especificos del evento
4) Copiar bloque .env generado por Beepers
5) Ejecutar bridge de hardware con ese .env

Importante: La respuesta incluye un api_key. Guardala de forma segura; no se vuelve a mostrar.

¿Para qué sirve la carpeta hardware?

La carpeta hardware/turnstile-bridge contiene un bridge de referencia en Bun/TypeScript para conectar lectores físicos (USB/RS485/ESP32) con Beepers por WebSocket.

hardware/turnstile-bridgebun
# En la raiz del proyecto
cd hardware/turnstile-bridge
bun install
cp .env.example .env
# pegar valores generados desde dashboard
bun run dev

2. Conectar por WebSocket

El molinete se conecta al WebSocket usando la API key para autenticarse.

Conexión WebSocketwebsocket
ws://tu-dominio.com/ws/turnstile?api_key=TU_API_KEY

Al conectar, recibiras:
{
  "action": "connected",
  "data": { "device_name": "Molinete Entrada Principal" }
}

3. Validar entradas

Cuando el lector QR del molinete lee un código, enviás un mensaje de validación y recibís la respuesta inmediata.

Enviar validaciónjson
{
  "action": "validate",
  "data": {
    "ticket_id": "ID_DEL_TICKET",
    "qr_data": "CONTENIDO_DEL_QR"
  }
}
Respuesta exitosajson
{
  "action": "validate_response",
  "allowed": true,
  "ticket_info": {
    "ticket_id": "...",
    "ticket_type": "VIP",
    "user_name": "Juan Perez"
  }
}
Respuesta denegadajson
{
  "action": "validate_response",
  "allowed": false,
  "reason": "ticket already scanned"
}

4. Keep-alive (Ping/Pong)

El servidor envía pings cada 30 segundos. Si no recibe respuesta en 60 segundos, cierra la conexión. También podés enviar un ping manual para mantener la sesión activa.

Ping manualjson
Enviar:  { "action": "ping" }
Respuesta: { "action": "pong" }

Ejemplo de integración en boliche

1

Hardware necesario

Raspberry Pi o mini-PC con lector QR USB (se recomienda Zebra DS9208 o similar).

2

Software

Script en Python o Node.js que lea del lector QR y se conecte al WebSocket. Al escanear un QR, envía el mensaje validate.

3

Acción

Si allowed: true, se activa el relay o solenoide del molinete para dejar pasar. Si es false, se muestra luz roja y se deniega el paso.

4

Reconexión

Implementá lógica de reconexión automática con backoff exponencial en caso de caída de la conexión WebSocket.

Ejemplos ejecutables (Node.js y Python)

bridge_client.tstypescript
import WebSocket from "ws"
import { SerialPort } from "serialport"
import { ReadlineParser } from "@serialport/parser-readline"

const API_KEY = process.env.TURNSTILE_API_KEY!
const EVENT_ID = process.env.TURNSTILE_EVENT_ID!
const WS_URL = `wss://tu-dominio.com/ws/turnstile?api_key=${encodeURIComponent(API_KEY)}`

const ws = new WebSocket(WS_URL)
const serial = new SerialPort({path: "/dev/ttyUSB0", baudRate: 9600 })
const parser = serial.pipe(new ReadlineParser({delimiter: "\\n" }))

ws.on("open", () => console.log("Conectado"))
ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString())
  if (msg.action === "validate_response") {
    console.log(msg.allowed ? "ACCESO PERMITIDO" : "ACCESO DENEGADO", msg.reason ?? "")
  }
})

parser.on("data", (line) => {
  const qr = String(line).trim()
  if (!qr) return
  ws.send(JSON.stringify({
    action: "validate",
    data: { qr_data: qr, event_id: EVENT_ID }
  }))
})
turnstile_client.pypython
import asyncio
import websockets
import json
import serial

API_KEY = "tu_api_key_aqui"
WS_URL = f"wss://tu-dominio.com/ws/turnstile?api_key={API_KEY}"

async def connect():
    async with websockets.connect(WS_URL) as ws:
        msg = await ws.recv()
        print("Conectado:", json.loads(msg))

        reader = serial.Serial("/dev/ttyUSB0", 9600)

        while True:
            if reader.in_waiting:
                qr_data = reader.readline().decode().strip()
                parts = qr_data.split("|")
                ticket_id = parts[0] if len(parts) > 1 else ""

                await ws.send(json.dumps({
                    "action": "validate",
                    "data": {
                        "ticket_id": ticket_id,
                        "qr_data": qr_data
                    }
                }))

                resp = json.loads(await ws.recv())
                if resp.get("allowed"):
                    print("ACCESO PERMITIDO:", resp["ticket_info"])
                    # activar_relay_del_molinete()
                else:
                    print("ACCESO DENEGADO:", resp.get("reason"))

            await asyncio.sleep(0.1)

asyncio.run(connect())

Checklist de producción

  • Usá siempre wss:// y nunca ws:// en ambientes reales.
  • Guardá la API key en el bridge, nunca en frontend ni apps públicas.
  • Configurá reconexión con backoff exponencial entre 1s y 30s.
  • Si tu lector solo emite código, enviá qr_data + event_id.
  • Rotá claves revocando el dispositivo anterior y creando uno nuevo.

Troubleshooting rápido

invalid or inactive api key: revisá que no esté revocada y que uses la última key.

device is not allowed for this event: el dispositivo pertenece a otro venue.

ticket not found or already used: código inválido o entrada ya escaneada.

device_rate_limited: reducí ráfaga de validaciones o separá por más lanes.