Zum Inhalt
RAWCaptureBooth Podręcznik użytkownika
Wersja 3.29.0

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 statusuWyzwalacze dla programów zewnętrznych
Kiedyw stałym odstępie, domyślnie co 30 sekundw chwili, gdy nastąpi dany krok
Copełny stan fotobudkipojedyncze zdarzenie z kilkoma wartościami
Dokądadres w internecie lub w sieciadres lub program na fotobudce
Typowowyś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#

  1. Przygotować odbiorcę: adres, który przyjmuje żądanie POST z JSON i odpowiada statusem od 200 do 299. Wystarczą do tego przykłady poniżej.
  2. W sekcji "Webhook statusu" wpisać adres i zapisać. Powstaje przy tym klucz do podpisu.
  3. Skopiować klucz przyciskiem "Kopiuj" i wpisać go u odbiorcy.
  4. Nacisnąć "Wyślij komunikat testowy". Poniżej pojawi się odpowiedź odbiorcy i wysłana treść.
  5. 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łówekTreść
Content-Typeapplication/json; charset=utf-8
User-AgentRAWCaptureBooth-Statuswebhook/<wersja>
X-RAWCaptureBooth-TimestampCzas komunikatu w sekundach od 1970 r. (czas uniksowy)
X-RAWCaptureBooth-Signaturesha256= 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.

PoleTreść
schema_versionBudowa komunikatu, obecnie 1
sent_atCzas wysłania w czasie lokalnym fotobudki, ze strefą czasową
report_interval_secondsOdstęp zgłoszeń w sekundach
computer_nameNazwa komputera fotobudki - identyfikator, gdy kilka fotobudek zgłasza się do tego samego odbiorcy
rawcapturebooth_versionZainstalowana wersja
rawcapturebooth_latest_versionNajnowsza opublikowana wersja, puste, dopóki fotobudka nie zdoła jej pobrać
cockpit_version, cockpit_latest_versionTo samo dla RAWCaptureBooth Cockpit, puste bez Cockpitu
uptimeCzas pracy od uruchomienia programu, na przykład 5:12:03 lub 1 day, 2:03:04
network_statusRzeczywista sytuacja sieciowa: LAN, WLAN (nazwa sieci), Online lub Offline
connectivity_modeUstawiony tryb pracy: online lub offline
email_enabled, print_enabled, cloud_enabledCzy wysyłka e-mail, drukowanie i przesyłanie do chmury są włączone
event_nameNazwa aktywnego wydarzenia
sessions_total, sessions_since_startSesje łącznie i od uruchomienia programu
session_limitMaksymalna liczba sesji, 0 oznacza brak limitu
total_print_jobs, print_jobs_since_start, print_limitTo samo dla zleceń druku
ai_portraits_total, ai_portraits_since_start, ai_portrait_limitTo samo dla portretów AI
ai_portrait_warn_thresholdOd tej liczby portretów AI fotobudka ostrzega, 0 oznacza wyłączone
last_session_at, last_capture_atCzas ostatniej sesji i ostatniego zdjęcia, zwykle w UTC (kończy się na Z)
error_countBłędy od uruchomienia programu
last_error, last_error_atOstatni błąd jako tekst, z czasem wystąpienia
cpu_used_percentObciążenie procesora od poprzedniego komunikatu, puste w pierwszym komunikacie po uruchomieniu
memory_used_percentZajęta pamięć operacyjna
disk_free_gb, disk_total_gbWolne i całkowite miejsce na dysku ze zdjęciami
outbox_pendingOczekujące e-maile i przesyłania, na przykład bez sieci
outbox_email_pending, outbox_cloud_pendingW tym e-maile i przesyłania do chmury
camera_battery_percentAkumulator aparatu w procentach, puste przy zasilaczu lub gdy aparat go nie zgłasza
camera_power_sourcebattery, ac (zasilacz) lub puste
printersJeden wpis na drukarkę, patrz niżej
buzzersJeden wpis na buzzer radiowy, patrz niżej

Wpis w printers:

PoleTreść
roleprint (drukarka zdjęć) lub strip (drukarka pasków)
modesingle (jedna drukarka) lub pool (pula drukarek, wtedy dodatkowo strategy)
nameNazwa drukarki w systemie Windows
availableCzy drukarka jest gotowa
stateStan, na przykład ready, printing, paused, error, offline lub unknown
queue_lengthZlecenia w kolejce
issuesLista zgłoszonych problemów, pusta, gdy ich brak
media_remaining, media_capacityPozostały materiał i pojemność w arkuszach, puste, gdy drukarka ich nie zgłasza
total_printsWydruki przez cały okres eksploatacji, tylko drukarki DNP

Wpis w buzzers:

PoleTreść
nameNazwa buzzera radiowego
connectionconnected, sleeping, disconnected, never (nigdy nie połączony) lub unavailable (Bluetooth niedostępny)
battery_levelBateria w trzech poziomach: 3 pełna, 2 połowa, 1 słaba, 0 nieznana
millivoltNapięcie baterii w miliwoltach
seconds_since_pressSekundy 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ą:

  1. Odczytać treść w takiej postaci, w jakiej przyszła, jako bajty - bez wcześniejszego wczytywania jako JSON i ponownego zapisywania.
  2. Połączyć czas z X-RAWCaptureBooth-Timestamp, kropkę i treść.
  3. Obliczyć z tego HMAC-SHA256 z kluczem, jako ciąg szesnastkowy, i dodać na początku sha256=.
  4. Porównać z X-RAWCaptureBooth-Signature, w stałym czasie.
  5. 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:// na https:// lub na adres z www. 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".

Aktualizacja: 01.10.2026 · Wersja 3.29.0