Zum Inhalt
RAWCaptureBooth Manual de usuario
Versión 4.3.0

Webhook de estado

El fotomatón informa periódicamente de su estado a una dirección que introduce el operador. Cada aviso es un JSON con los mismos valores que muestra el portal de la administración remota para este fotomatón. Lo que haga el receptor con él lo decide el propio receptor.

Se activa en el área de administración, "Red y administración remota" > "Webhook de estado". La administración remota no es necesaria para ello: el webhook funciona también en un fotomatón sin portal.

Para qué sirve#

  • Mostrar en el sitio web propio si el fotomatón está en uso en ese momento y cuántas fotos se han hecho ya.
  • Llevar una vista general propia de varios fotomatones en un sistema existente.
  • Recibir avisos cuando se acaba el papel, baja la batería de la cámara o un fotomatón deja de informar, mediante un sistema domótico o un chat.
  • Registrar contadores, por ejemplo para la facturación con el cliente.

¿Webhook de estado o disparadores?#

Ambos informan hacia fuera, pero de forma distinta:

Webhook de estadoDisparadores para programas externos
Cuándoa intervalos fijos, de fábrica cada 30 segundosen el momento en que ocurre un paso
Quéel estado completo del fotomatónun único evento con pocos valores
Adóndeuna dirección de internet o de la reduna dirección o un programa en el fotomatón
Típicovisualización, vigilancia, estadísticasluces, enchufes, scripts en el flujo

Ambos se pueden usar a la vez. Los disparadores se describen en el capítulo Disparadores para programas externos.

Configuración#

  1. Preparar un receptor: una dirección que acepte una petición POST con JSON y responda con un estado de 200 a 299. Los ejemplos de más abajo bastan para ello.
  2. En la sección "Webhook de estado", introducir la dirección y guardar. Así se genera la clave de la firma.
  3. Copiar la clave con "Copiar" e introducirla en el receptor.
  4. Pulsar "Enviar aviso de prueba". Debajo aparecen la respuesta del receptor y el contenido enviado.
  5. Activar "Usar webhook de estado". El primer aviso sale al instante y después con el intervalo ajustado.

Qué llega#

Cada aviso es una petición POST con estos encabezados:

EncabezadoContenido
Content-Typeapplication/json; charset=utf-8
User-AgentRAWCaptureBooth-Statuswebhook/<Version>
X-RAWCaptureBooth-TimestampMomento del aviso en segundos desde 1970 (hora Unix)
X-RAWCaptureBooth-Signaturesha256= y la firma; véase más abajo

El contenido tiene este aspecto (reducido a una impresora y un pulsador):

{
  "schema_version": 1,
  "sent_at": "2026-10-03T21:14:05+02:00",
  "report_interval_seconds": 30,
  "computer_name": "FOTOBOX-01",
  "rawcapturebooth_version": "3.28.0",
  "rawcapturebooth_latest_version": "3.28.0",
  "cockpit_version": null,
  "cockpit_latest_version": null,
  "uptime": "5:12:03",
  "network_status": "WLAN (Festsaal)",
  "connectivity_mode": "online",
  "email_enabled": true,
  "print_enabled": true,
  "cloud_enabled": false,
  "event_name": "Hochzeit Müller",
  "sessions_total": 1843,
  "sessions_since_start": 112,
  "session_limit": 0,
  "total_print_jobs": 2210,
  "print_jobs_since_start": 131,
  "print_limit": 300,
  "ai_portraits_total": 0,
  "ai_portraits_since_start": 0,
  "ai_portrait_limit": 0,
  "ai_portrait_warn_threshold": 0,
  "last_session_at": "2026-10-03T19:13:40Z",
  "last_capture_at": "2026-10-03T19:13:40Z",
  "error_count": 0,
  "last_error": null,
  "last_error_at": null,
  "cpu_used_percent": 23.5,
  "memory_used_percent": 61.2,
  "disk_free_gb": 182.4,
  "disk_total_gb": 476.3,
  "outbox_pending": 0,
  "outbox_email_pending": 0,
  "outbox_cloud_pending": 0,
  "camera_battery_percent": null,
  "camera_power_source": "ac",
  "printers": [
    {
      "role": "print",
      "mode": "single",
      "name": "DP-DS620",
      "available": true,
      "state": "ready",
      "queue_length": 0,
      "issues": [],
      "media_remaining": 268,
      "media_capacity": 400,
      "total_prints": 15320
    }
  ],
  "buzzers": [
    {
      "name": "Buzzer 1",
      "connection": "connected",
      "battery_level": 3,
      "millivolt": 4010,
      "seconds_since_press": 25
    }
  ],
  "lightcontrol": [
    {
      "id": "7F3A92C1",
      "name": "Licht links",
      "model": "LightControl V2 Temp",
      "connected": true,
      "temperatures": [
        { "sensor": "28FF000000000001", "celsius": 31.44, "alarm": null }
      ]
    }
  ]
}

El contenido real del propio fotomatón lo muestra "Enviar aviso de prueba" en "Contenido enviado".

Los campos#

Un campo sin valor aparece como null. En versiones posteriores pueden añadirse campos nuevos; un receptor omite lo que no conoce. schema_version solo sube cuando cambia el significado de un campo o desaparece alguno.

CampoContenido
schema_versionEstructura del aviso, actualmente 1
sent_atMomento del envío en la hora local del fotomatón, con zona horaria
report_interval_secondsIntervalo de aviso en segundos
computer_nameNombre del ordenador del fotomatón: el identificador cuando varias cabinas informan al mismo receptor
rawcapturebooth_versionVersión instalada
rawcapturebooth_latest_versionVersión publicada más reciente; vacío mientras el fotomatón no la haya podido consultar
cockpit_version, cockpit_latest_versionLo mismo para RAWCaptureBooth Cockpit; vacío sin Cockpit
uptimeTiempo de funcionamiento desde el inicio del programa, por ejemplo 5:12:03 o 1 day, 2:03:04
network_statusSituación real de la red: LAN, WLAN (Netzname), Online u Offline
connectivity_modeModo de funcionamiento ajustado: online u offline
email_enabled, print_enabled, cloud_enabledSi están activados el envío de correo, la impresión y la subida a la nube
event_nameNombre del evento activo
sessions_total, sessions_since_startSesiones en total y desde el inicio del programa
session_limitLímite de sesiones; 0 significa sin límite
total_print_jobs, print_jobs_since_start, print_limitLo mismo para los trabajos de impresión
ai_portraits_total, ai_portraits_since_start, ai_portrait_limitLo mismo para los retratos con IA
ai_portrait_warn_thresholdA partir de este número de retratos con IA avisa el fotomatón; 0 significa desactivado
last_session_at, last_capture_atMomento de la última sesión y de la última captura, casi siempre en UTC (termina en Z)
error_countErrores desde el inicio del programa
last_error, last_error_atEl último error como texto, con el momento
cpu_used_percentCarga del procesador desde el último aviso; vacío en el primer aviso tras el inicio
memory_used_percentMemoria de trabajo ocupada
disk_free_gb, disk_total_gbEspacio libre y total de la unidad con las capturas
outbox_pendingCorreos y subidas pendientes, por ejemplo sin red
outbox_email_pending, outbox_cloud_pendingDe ellos, correos y subidas a la nube
camera_battery_percentBatería de la cámara en porcentaje; vacío con fuente de alimentación o si la cámara no lo indica
camera_power_sourcebattery, ac (fuente de alimentación) o vacío
printersUna entrada por impresora; véase más abajo
buzzersUna entrada por pulsador inalámbrico; véase más abajo
lightcontrolUna entrada por LightControl; véase más abajo; vacío mientras el LightControl esté desactivado

Una entrada de printers:

CampoContenido
roleprint (impresora de fotos) o strip (impresora de tiras de fotos)
modesingle (una impresora) o pool (pool de impresoras; entonces además strategy)
nameNombre de la impresora en Windows
availableSi la impresora está lista
stateEstado, por ejemplo ready, printing, paused, error, offline o unknown
queue_lengthTrabajos en la cola
issuesLista de problemas comunicados; vacía si no hay ninguno
media_remaining, media_capacityMaterial restante y capacidad en hojas; vacío si la impresora no lo indica
total_printsImpresiones a lo largo de la vida útil, solo en impresoras DNP

Una entrada de buzzers:

CampoContenido
nameNombre del pulsador inalámbrico
connectionconnected, sleeping, disconnected, never (nunca conectado) o unavailable (Bluetooth no disponible)
battery_levelBatería en tres niveles: 3 llena, 2 a la mitad, 1 baja, 0 desconocida
millivoltTensión de la batería en milivoltios
seconds_since_pressSegundos desde la última pulsación; vacío si aún no se ha pulsado

Una entrada de lightcontrol:

CampoContenido
idIdentificador del controlador
nameNombre del área de administración; vacío sin nombre
modelLightControl V2, LightControl V2 Temp o LightControl V2 Mini
connectedSi el controlador está conectado
temperaturesPor sensor, sensor (dirección), celsius y alarm (high, low o vacío)

Comprobar la firma#

Cada aviso va firmado con la clave del área de administración. El receptor calcula la firma por sí mismo y solo acepta el aviso si ambas coinciden:

  1. Leer el contenido tal como llega, como bytes, sin interpretarlo antes como JSON y volver a generarlo.
  2. Unir la marca de tiempo de X-RAWCaptureBooth-Timestamp, un punto y el contenido.
  3. Calcular sobre ello HMAC-SHA256 con la clave, como cadena hexadecimal, y anteponer sha256=.
  4. Compararla con X-RAWCaptureBooth-Signature en tiempo constante.
  5. Rechazar los avisos cuya marca de tiempo difiera más de cinco minutos de la hora propia. Así no se puede reenviar más tarde un aviso interceptado.

"Generar nueva" en el área de administración sustituye la clave. Hasta que se actualice en el receptor, este rechaza todos los avisos.

Ejemplo: mostrarlo en el sitio web propio#

Un sitio web no puede aceptar por sí mismo un aviso: necesita un pequeño script en el servidor. Este script PHP comprueba la firma y guarda solo los valores que deben ser públicos en un archivo fotobox-status.json:

<?php
// fotobox-webhook.php - indicar como dirección en el área de administración
$secret = 'PEGAR-AQUI-LA-CLAVE';

$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_RAWCAPTUREBOOTH_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_RAWCAPTUREBOOTH_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);

if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit;
}
$status = json_decode($body, true);
if (!is_array($status)) {
    http_response_code(400);
    exit;
}

// Pasar solo lo que debe ir en el sitio web
$public = [
    'event_name' => $status['event_name'] ?? null,
    'sessions_since_start' => $status['sessions_since_start'] ?? 0,
    'report_interval_seconds' => $status['report_interval_seconds'] ?? 30,
    'received_at' => time(),
];
file_put_contents(__DIR__ . '/fotobox-status.json', json_encode($public), LOCK_EX);
http_response_code(204);

El sitio web lee el archivo y decide por sí mismo si el fotomatón está disponible:

<p id="fotobox">Fotomatón: cargando …</p>
<script>
fetch('/fotobox-status.json', { cache: 'no-store' })
  .then((response) => response.json())
  .then((status) => {
    const age = Date.now() / 1000 - status.received_at;
    const online = age <= 2 * status.report_interval_seconds;
    document.getElementById('fotobox').textContent = online
      ? 'Fotomatón en uso, ' + status.sessions_since_start + ' sesiones desde el inicio'
      : 'Fotomatón no disponible en este momento';
  });
</script>

Ejemplo: receptor en Node.js#

Sin paquetes adicionales, con el módulo http integrado:

const http = require('http');
const crypto = require('crypto');

const SECRET = 'PEGAR-AQUI-LA-CLAVE';

http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const body = Buffer.concat(chunks);
    const timestamp = req.headers['x-rawcapturebooth-timestamp'] || '';
    const signature = req.headers['x-rawcapturebooth-signature'] || '';
    const expected = 'sha256=' + crypto.createHmac('sha256', SECRET)
      .update(timestamp + '.').update(body).digest('hex');
    const valid = signature.length === expected.length
      && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
      && Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
    if (!valid) {
      res.writeHead(401).end();
      return;
    }
    const status = JSON.parse(body.toString('utf8'));
    console.log(status.computer_name, status.event_name, status.sessions_total);
    res.writeHead(204).end();
  });
}).listen(8080);

Detectar que un fotomatón ya no informa#

Un fotomatón no puede comunicar por sí mismo que está "sin conexión": sin conexión tampoco llega ningún aviso. Por eso el receptor recuerda cuándo llegó el último aviso. Si pasa más de dos intervalos de aviso (report_interval_seconds) sin llegar, el fotomatón no está disponible. Dos intervalos en lugar de uno absorben un único aviso retrasado.

Solución de problemas#

  • "Enviar aviso de prueba" indica HTTP 401 o 403: el receptor rechaza la firma. ¿Es correcta la clave? ¿Se comprueba el contenido sin procesar (véase arriba)? ¿Va bien el reloj del servidor?
  • HTTP 404: la dirección no es correcta; revisar la ruta y el nombre del archivo.
  • Redirección: la dirección redirige, a menudo de http:// a https:// o a una dirección con www. El fotomatón no sigue redirecciones; introducir directamente la dirección de destino que indica el aviso.
  • Sin respuesta en 15 segundos: el receptor no está disponible o responde demasiado despacio. Debería aceptar el aviso y responder al instante, en lugar de seguir trabajando primero.
  • Los avisos llegan con menos frecuencia de lo ajustado: tras un fallo, el fotomatón espera más hasta el siguiente intento: primero 15 segundos, después el doble, como máximo un cuarto de hora. Con el primer éxito vuelve a valer el intervalo ajustado. Cuántos intentos seguidos han fallado figura en la sección, en "Prueba y estado".

Actualizado: 06.11.2026 · Versión 4.3.0