Webhook statusu
Fotobudka regularnie przesyła swój stan pod adres wpisany przez operatora. Każdy komunikat to JSON z tymi samymi wartościami, które portal zarządzania zdalnego pokazuje dla tej fotobudki. Co odbiorca z nimi zrobi, zależy tylko od niego.
Włącza się to w panelu administracyjnym "Oświetlenie i akcesoria" > "Webhook statusu". Zarządzanie zdalne nie jest do tego potrzebne: webhook działa także na fotobudce bez portalu.
Do czego to służy#
- Pokazywanie na własnej stronie, czy fotobudka jest właśnie w użyciu i ile zdjęć już powstało.
- Własny przegląd kilku fotobudek, w już używanym systemie.
- Powiadomienia, gdy kończy się papier, słabnie akumulator aparatu albo fotobudka przestaje się zgłaszać - przez automatykę domową lub czat.
- Zapisywanie liczników, na przykład do rozliczenia z klientem.
Webhook statusu czy wyzwalacze?#
Oba przesyłają dane na zewnątrz, ale w inny sposób:
| Webhook statusu | Wyzwalacze dla programów zewnętrznych | |
|---|---|---|
| Kiedy | w stałym odstępie, domyślnie co 30 sekund | w chwili, gdy nastąpi dany krok |
| Co | pełny stan fotobudki | pojedyncze zdarzenie z kilkoma wartościami |
| Dokąd | adres w internecie lub w sieci | adres lub program na fotobudce |
| Typowo | wyświetlanie, monitorowanie, statystyki | światło, gniazdka, skrypty w trakcie sesji |
Obu można używać jednocześnie. Wyzwalacze opisuje rozdział Wyzwalacze dla programów zewnętrznych.
Konfiguracja#
- Przygotować odbiorcę: adres, który przyjmuje żądanie POST z JSON i odpowiada statusem od 200 do 299. Wystarczą do tego przykłady poniżej.
- W sekcji "Webhook statusu" wpisać adres i zapisać. Powstaje przy tym klucz do podpisu.
- Skopiować klucz przyciskiem "Kopiuj" i wpisać go u odbiorcy.
- Nacisnąć "Wyślij komunikat testowy". Poniżej pojawi się odpowiedź odbiorcy i wysłana treść.
- Włączyć "Używaj webhooka statusu". Pierwszy komunikat wychodzi od razu, potem w ustawionym odstępie.
Co przychodzi#
Każdy komunikat to żądanie POST z tymi nagłówkami:
| Nagłówek | Treść |
|---|---|
Content-Type | application/json; charset=utf-8 |
User-Agent | RAWCaptureBooth-Statuswebhook/<wersja> |
X-RAWCaptureBooth-Timestamp | Czas komunikatu w sekundach od 1970 r. (czas uniksowy) |
X-RAWCaptureBooth-Signature | sha256= i podpis, patrz niżej |
Treść wygląda tak (skrócona do jednej drukarki i jednego buzzera):
{
"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 (Sala bankietowa)",
"connectivity_mode": "online",
"email_enabled": true,
"print_enabled": true,
"cloud_enabled": false,
"event_name": "Wesele Kowalskich",
"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
}
]
}
"Wyślij komunikat testowy" pokazuje prawdziwą treść własnej fotobudki w polu "Wysłana treść".
Pola#
Pole bez wartości ma postać null. W późniejszych wersjach mogą dojść nowe pola; odbiorca pomija to, czego nie zna. schema_version rośnie tylko wtedy, gdy zmienia się znaczenie pola albo pole znika.
| Pole | Treść |
|---|---|
schema_version | Budowa komunikatu, obecnie 1 |
sent_at | Czas wysłania w czasie lokalnym fotobudki, ze strefą czasową |
report_interval_seconds | Odstęp zgłoszeń w sekundach |
computer_name | Nazwa komputera fotobudki - identyfikator, gdy kilka fotobudek zgłasza się do tego samego odbiorcy |
rawcapturebooth_version | Zainstalowana wersja |
rawcapturebooth_latest_version | Najnowsza opublikowana wersja, puste, dopóki fotobudka nie zdoła jej pobrać |
cockpit_version, cockpit_latest_version | To samo dla RAWCaptureBooth Cockpit, puste bez Cockpitu |
uptime | Czas pracy od uruchomienia programu, na przykład 5:12:03 lub 1 day, 2:03:04 |
network_status | Rzeczywista sytuacja sieciowa: LAN, WLAN (nazwa sieci), Online lub Offline |
connectivity_mode | Ustawiony tryb pracy: online lub offline |
email_enabled, print_enabled, cloud_enabled | Czy wysyłka e-mail, drukowanie i przesyłanie do chmury są włączone |
event_name | Nazwa aktywnego wydarzenia |
sessions_total, sessions_since_start | Sesje łącznie i od uruchomienia programu |
session_limit | Maksymalna liczba sesji, 0 oznacza brak limitu |
total_print_jobs, print_jobs_since_start, print_limit | To samo dla zleceń druku |
ai_portraits_total, ai_portraits_since_start, ai_portrait_limit | To samo dla portretów AI |
ai_portrait_warn_threshold | Od tej liczby portretów AI fotobudka ostrzega, 0 oznacza wyłączone |
last_session_at, last_capture_at | Czas ostatniej sesji i ostatniego zdjęcia, zwykle w UTC (kończy się na Z) |
error_count | Błędy od uruchomienia programu |
last_error, last_error_at | Ostatni błąd jako tekst, z czasem wystąpienia |
cpu_used_percent | Obciążenie procesora od poprzedniego komunikatu, puste w pierwszym komunikacie po uruchomieniu |
memory_used_percent | Zajęta pamięć operacyjna |
disk_free_gb, disk_total_gb | Wolne i całkowite miejsce na dysku ze zdjęciami |
outbox_pending | Oczekujące e-maile i przesyłania, na przykład bez sieci |
outbox_email_pending, outbox_cloud_pending | W tym e-maile i przesyłania do chmury |
camera_battery_percent | Akumulator aparatu w procentach, puste przy zasilaczu lub gdy aparat go nie zgłasza |
camera_power_source | battery, ac (zasilacz) lub puste |
printers | Jeden wpis na drukarkę, patrz niżej |
buzzers | Jeden wpis na buzzer radiowy, patrz niżej |
Wpis w printers:
| Pole | Treść |
|---|---|
role | print (drukarka zdjęć) lub strip (drukarka pasków) |
mode | single (jedna drukarka) lub pool (pula drukarek, wtedy dodatkowo strategy) |
name | Nazwa drukarki w systemie Windows |
available | Czy drukarka jest gotowa |
state | Stan, na przykład ready, printing, paused, error, offline lub unknown |
queue_length | Zlecenia w kolejce |
issues | Lista zgłoszonych problemów, pusta, gdy ich brak |
media_remaining, media_capacity | Pozostały materiał i pojemność w arkuszach, puste, gdy drukarka ich nie zgłasza |
total_prints | Wydruki przez cały okres eksploatacji, tylko drukarki DNP |
Wpis w buzzers:
| Pole | Treść |
|---|---|
name | Nazwa buzzera radiowego |
connection | connected, sleeping, disconnected, never (nigdy nie połączony) lub unavailable (Bluetooth niedostępny) |
battery_level | Bateria w trzech poziomach: 3 pełna, 2 połowa, 1 słaba, 0 nieznana |
millivolt | Napięcie baterii w miliwoltach |
seconds_since_press | Sekundy od ostatniego naciśnięcia, puste, jeśli jeszcze nie naciśnięto |
Sprawdzanie podpisu#
Każdy komunikat jest podpisany kluczem z panelu administracyjnego. Odbiorca sam oblicza podpis i przyjmuje komunikat tylko wtedy, gdy oba się zgadzają:
- Odczytać treść w takiej postaci, w jakiej przyszła, jako bajty - bez wcześniejszego wczytywania jako JSON i ponownego zapisywania.
- Połączyć czas z
X-RAWCaptureBooth-Timestamp, kropkę i treść. - Obliczyć z tego HMAC-SHA256 z kluczem, jako ciąg szesnastkowy, i dodać na początku
sha256=. - Porównać z
X-RAWCaptureBooth-Signature, w stałym czasie. - Odrzucać komunikaty, których czas odbiega od własnego zegara o więcej niż pięć minut. Dzięki temu przechwyconego komunikatu nie da się później wysłać ponownie.
"Wygeneruj nowy" w panelu administracyjnym zastępuje klucz. Dopóki nie zostanie wpisany u odbiorcy, ten odrzuca każdy komunikat.
Przykład: wyświetlanie na własnej stronie#
Strona internetowa nie przyjmie komunikatu sama - potrzebny jest mały skrypt na serwerze. Ten skrypt PHP sprawdza podpis i zapisuje w pliku fotobox-status.json tylko te wartości, które mają być publiczne:
<?php
// fotobox-webhook.php - wpisać jako adres w panelu administracyjnym
$secret = 'TUTAJ-WPISAC-KLUCZ';
$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;
}
// Przekazywać tylko to, co ma trafić na stronę
$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);
Strona odczytuje plik i sama decyduje, czy fotobudka jest osiągalna:
<p id="fotobox">Fotobudka: wczytywanie …</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
? 'Fotobudka w użyciu, ' + status.sessions_since_start + ' sesji od uruchomienia'
: 'Fotobudka jest obecnie nieosiągalna';
});
</script>
Przykład: odbiorca w Node.js#
Bez dodatkowych pakietów, z wbudowanym modułem http:
const http = require('http');
const crypto = require('crypto');
const SECRET = 'TUTAJ-WPISAC-KLUCZ';
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);
Rozpoznawanie, że fotobudka przestała się zgłaszać#
Fotobudka nie może sama zgłosić, że jest "offline" - bez połączenia nie dociera też żaden komunikat. Odbiorca zapamiętuje więc, kiedy przyszedł ostatni komunikat. Jeśli nie przyjdzie żaden przez dłużej niż dwa odstępy (report_interval_seconds), fotobudka jest nieosiągalna. Dwa odstępy zamiast jednego wyłapują pojedynczy opóźniony komunikat.
Rozwiązywanie problemów#
- "Wyślij komunikat testowy" zwraca HTTP 401 lub 403: Odbiorca odrzuca podpis. Czy klucz jest poprawny? Czy sprawdzana jest surowa treść (patrz wyżej)? Czy zegar serwera jest ustawiony prawidłowo?
- HTTP 404: Adres jest błędny - sprawdzić ścieżkę i nazwę pliku.
- "Przekierowanie do …": Adres przekierowuje, często z
http://nahttps://lub na adres zwww. Fotobudka nie wykonuje przekierowań; wpisać bezpośrednio adres docelowy podany w komunikacie. - "Brak odpowiedzi w ciągu 15 sekund": Odbiorca jest nieosiągalny lub odpowiada zbyt wolno. Powinien przyjąć komunikat i od razu odpowiedzieć, zamiast najpierw go przetwarzać.
- Komunikaty przychodzą rzadziej, niż ustawiono: Po niepowodzeniu fotobudka czeka dłużej do następnej próby - najpierw 15 sekund, potem za każdym razem dwa razy dłużej, najwyżej kwadrans. Po pierwszym sukcesie znów obowiązuje ustawiony odstęp. Ile prób z rzędu się nie powiodło, widać w sekcji "Test i stan".