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 estado | Disparadores para programas externos | |
|---|---|---|
| Cuándo | a intervalos fijos, de fábrica cada 30 segundos | en el momento en que ocurre un paso |
| Qué | el estado completo del fotomatón | un único evento con pocos valores |
| Adónde | una dirección de internet o de la red | una dirección o un programa en el fotomatón |
| Típico | visualización, vigilancia, estadísticas | luces, 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#
- 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.
- En la sección "Webhook de estado", introducir la dirección y guardar. Así se genera la clave de la firma.
- Copiar la clave con "Copiar" e introducirla en el receptor.
- Pulsar "Enviar aviso de prueba". Debajo aparecen la respuesta del receptor y el contenido enviado.
- 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:
| Encabezado | Contenido |
|---|---|
Content-Type | application/json; charset=utf-8 |
User-Agent | RAWCaptureBooth-Statuswebhook/<Version> |
X-RAWCaptureBooth-Timestamp | Momento del aviso en segundos desde 1970 (hora Unix) |
X-RAWCaptureBooth-Signature | sha256= 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.
| Campo | Contenido |
|---|---|
schema_version | Estructura del aviso, actualmente 1 |
sent_at | Momento del envío en la hora local del fotomatón, con zona horaria |
report_interval_seconds | Intervalo de aviso en segundos |
computer_name | Nombre del ordenador del fotomatón: el identificador cuando varias cabinas informan al mismo receptor |
rawcapturebooth_version | Versión instalada |
rawcapturebooth_latest_version | Versión publicada más reciente; vacío mientras el fotomatón no la haya podido consultar |
cockpit_version, cockpit_latest_version | Lo mismo para RAWCaptureBooth Cockpit; vacío sin Cockpit |
uptime | Tiempo de funcionamiento desde el inicio del programa, por ejemplo 5:12:03 o 1 day, 2:03:04 |
network_status | Situación real de la red: LAN, WLAN (Netzname), Online u Offline |
connectivity_mode | Modo de funcionamiento ajustado: online u offline |
email_enabled, print_enabled, cloud_enabled | Si están activados el envío de correo, la impresión y la subida a la nube |
event_name | Nombre del evento activo |
sessions_total, sessions_since_start | Sesiones en total y desde el inicio del programa |
session_limit | Límite de sesiones; 0 significa sin límite |
total_print_jobs, print_jobs_since_start, print_limit | Lo mismo para los trabajos de impresión |
ai_portraits_total, ai_portraits_since_start, ai_portrait_limit | Lo mismo para los retratos con IA |
ai_portrait_warn_threshold | A partir de este número de retratos con IA avisa el fotomatón; 0 significa desactivado |
last_session_at, last_capture_at | Momento de la última sesión y de la última captura, casi siempre en UTC (termina en Z) |
error_count | Errores desde el inicio del programa |
last_error, last_error_at | El último error como texto, con el momento |
cpu_used_percent | Carga del procesador desde el último aviso; vacío en el primer aviso tras el inicio |
memory_used_percent | Memoria de trabajo ocupada |
disk_free_gb, disk_total_gb | Espacio libre y total de la unidad con las capturas |
outbox_pending | Correos y subidas pendientes, por ejemplo sin red |
outbox_email_pending, outbox_cloud_pending | De ellos, correos y subidas a la nube |
camera_battery_percent | Batería de la cámara en porcentaje; vacío con fuente de alimentación o si la cámara no lo indica |
camera_power_source | battery, ac (fuente de alimentación) o vacío |
printers | Una entrada por impresora; véase más abajo |
buzzers | Una entrada por pulsador inalámbrico; véase más abajo |
lightcontrol | Una entrada por LightControl; véase más abajo; vacío mientras el LightControl esté desactivado |
Una entrada de printers:
| Campo | Contenido |
|---|---|
role | print (impresora de fotos) o strip (impresora de tiras de fotos) |
mode | single (una impresora) o pool (pool de impresoras; entonces además strategy) |
name | Nombre de la impresora en Windows |
available | Si la impresora está lista |
state | Estado, por ejemplo ready, printing, paused, error, offline o unknown |
queue_length | Trabajos en la cola |
issues | Lista de problemas comunicados; vacía si no hay ninguno |
media_remaining, media_capacity | Material restante y capacidad en hojas; vacío si la impresora no lo indica |
total_prints | Impresiones a lo largo de la vida útil, solo en impresoras DNP |
Una entrada de buzzers:
| Campo | Contenido |
|---|---|
name | Nombre del pulsador inalámbrico |
connection | connected, sleeping, disconnected, never (nunca conectado) o unavailable (Bluetooth no disponible) |
battery_level | Batería en tres niveles: 3 llena, 2 a la mitad, 1 baja, 0 desconocida |
millivolt | Tensión de la batería en milivoltios |
seconds_since_press | Segundos desde la última pulsación; vacío si aún no se ha pulsado |
Una entrada de lightcontrol:
| Campo | Contenido |
|---|---|
id | Identificador del controlador |
name | Nombre del área de administración; vacío sin nombre |
model | LightControl V2, LightControl V2 Temp o LightControl V2 Mini |
connected | Si el controlador está conectado |
temperatures | Por 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:
- Leer el contenido tal como llega, como bytes, sin interpretarlo antes como JSON y volver a generarlo.
- Unir la marca de tiempo de
X-RAWCaptureBooth-Timestamp, un punto y el contenido. - Calcular sobre ello HMAC-SHA256 con la clave, como cadena hexadecimal, y anteponer
sha256=. - Compararla con
X-RAWCaptureBooth-Signatureen tiempo constante. - 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://ahttps://o a una dirección conwww. 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".