Zum Inhalt springen
Anleitung

Bei Offline eine Nachricht in Teams oder Slack

Ein kleiner Empfänger macht aus dem Webhook für Zustandswechsel eine Nachricht im Team-Chat.

3 Min LesezeitStand 30. Okt. 2026Als Markdown
Für wen
Entwicklerinnen und Entwickler, die Displays für ein Team betreuen, das in Slack oder Microsoft Teams arbeitet
Voraussetzung
ein Paket mit Webhooks (ab Premium), die Rolle Inhaber für den Endpunkt, ein Schlüssel ak_… der Klasse Nur Lesen für die Namen der Displays und ein Server mit öffentlicher https-Adresse
Ort
Ihr eigener Server und Einstellungen › Für Entwickler › Webhooks
Auf dieser Seite

Am Ende schreibt ein kleiner Empfänger eine Nachricht in Ihren Team-Chat, sobald ein Display offline geht, und noch eine, wenn es zurück ist. Niemand muss die Flotte offen halten, um zu merken, dass der Fernseher im Schaufenster schwarz ist.

So funktioniert es

Szenera kann nicht direkt in einen Chat schreiben: Der Chat erwartet sein eigenes Nachrichtenformat, und Szenera signiert jede Zustellung. Dazwischen sitzt ein eigener Empfänger:

  1. Szenera schickt das Ereignis fleet.player.status-changed mit playerId, from und to.
  2. Der Empfänger prüft die Signatur, übergeht Wiederholungen und liest den Namen des Displays über die API.
  3. Er schickt eine Nachricht an den eingehenden Webhook Ihres Chats.

Als offline gilt ein Display, das sich fünf Minuten nicht gemeldet hat. Davor ist es kurz stale. Der Empfänger meldet nur den Schritt nach offline und die Rückkehr nach online, damit ein kurzes Wackeln den Chat nicht flutet.

Schritte

  1. Legen Sie im Chat den eingehenden Webhook an.

    • Slack: Legen Sie eine App mit Incoming Webhooks für den Kanal an. Die Adresse beginnt mit https://hooks.slack.com/.
    • Microsoft Teams: Öffnen Sie im Kanal die Workflows und legen Sie aus einer Vorlage einen Ablauf an, der eingehende Webhook-Anfragen in den Kanal schreibt. Der Ablauf gibt Ihnen eine Adresse, die Adaptive Cards annimmt.
  2. Speichern Sie den Empfänger auf Ihrem Server und starten Sie ihn mit den vier Werten:

    TypeScript
    // offline-alert.ts: node offline-alert.tsimport { createServer } from 'node:http';import { createHmac, timingSafeEqual } from 'node:crypto'; const SECRET = process.env.WEBHOOK_SECRET ?? ''; // whsec_…const API = process.env.SZENERA_API;const KEY = process.env.SZENERA_API_KEY; // Klasse Nur Lesenconst CHAT = process.env.CHAT_WEBHOOK_URL ?? ''; // Slack oder Teamsconst seen = new Set<string>(); function isFromSignage(rawBody: string, header: string): boolean {  const expected = createHmac('sha256', SECRET).update(rawBody).digest('base64url');  const a = Buffer.from(expected);  const b = Buffer.from(header);  return a.length === b.length && timingSafeEqual(a, b);} async function displayName(playerId: string): Promise<string> {  const res = await fetch(`${API}/v1/players/${playerId}`, {    headers: { Authorization: `Bearer ${KEY}` },  });  return res.ok ? (await res.json()).player.name : playerId;} function chatMessage(text: string): unknown {  if (CHAT.startsWith('https://hooks.slack.com/')) return { text };  return {    type: 'message',    attachments: [      {        contentType: 'application/vnd.microsoft.card.adaptive',        content: { type: 'AdaptiveCard', version: '1.4', body: [{ type: 'TextBlock', text, wrap: true }] },      },    ],  };} createServer((req, res) => {  const chunks: Buffer[] = [];  req.on('data', (chunk) => chunks.push(chunk));  req.on('end', async () => {    const raw = Buffer.concat(chunks).toString('utf8');    if (!isFromSignage(raw, String(req.headers['x-signature'] ?? ''))) {      res.writeHead(401).end();      return;    }    res.writeHead(204).end(); // Binnen 10 Sekunden antworten, danach arbeiten     const deliveryId = String(req.headers['x-delivery-id']);    if (seen.has(deliveryId) || req.headers['x-event-type'] !== 'fleet.player.status-changed') return;    seen.add(deliveryId);     const { playerId, from, to } = JSON.parse(raw);    let text = '';    if (to === 'offline') text = `${await displayName(playerId)} is offline.`;    if (to === 'online' && from === 'offline') text = `${await displayName(playerId)} is back online.`;    if (!text) return;     await fetch(CHAT, {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify(chatMessage(text)),    });  });}).listen(8080);
    Terminal
    export WEBHOOK_SECRET="whsec_…" CHAT_WEBHOOK_URL="https://hooks.slack.com/services/…"node offline-alert.ts

    Stellen Sie den Empfänger hinter Ihre https-Adresse, zum Beispiel https://alerts.example.com/szenera. Die Menge seen lebt im Speicher. Startet der Empfänger neu, kann eine Wiederholung eine zweite Nachricht auslösen, was hier nicht schadet. Für alles, was nur einmal geschehen darf, speichern Sie die Kennungen.

  3. Legen Sie den Endpunkt an, im Portal unter Einstellungen › Für Entwickler › Webhooks mit Endpunkt anlegen. Tragen Sie die Adresse unter Endpunkt-URL ein, wählen Sie unter Abonnierte Ereignisse fleet.player.status-changed und klicken Sie auf Anlegen. Kopieren Sie das Secret aus Signatur-Secret — nur jetzt sichtbar nach WEBHOOK_SECRET und starten Sie den Empfänger neu.

    Die Route POST /v1/webhooks geht auch, aber nur mit dem Zugangs-Token einer Anmeldung als Inhaber. Ein API-Schlüssel kann die Webhook-Rechte nicht tragen, siehe Einen Webhook-Endpunkt anlegen.

  4. Probieren Sie es aus. Ziehen Sie den Netzwerkstecker eines Displays oder schließen Sie den Browser-Tab eines Web-Displays. Nach etwa fünf Minuten kommt die Nachricht im Chat an. Verbinden Sie es wieder, folgt die zweite.

Ergebnis prüfen

  • Im Zustell-Log des Endpunkts zeigt jede Zustellung Zugestellt mit HTTP 204.
  • Der Chat zeigt den Namen des Displays, nicht seine Kennung. Steht dort die Kennung, kann der Schlüssel das Display nicht lesen.
  • Eine Zustellung, die Sie im Zustell-Log mit Erneut zustellen wiederholen, löst keine zweite Nachricht aus.

Wenn etwas nicht klappt

Symptom Bedeutung Lösung
Das Zustell-Log zeigt HTTP 401 Die Signatur passt nicht: falsches Secret, oder ein Proxy verändert den Rumpf. Das Secret neu kopieren und sicherstellen, dass nichts den Rumpf vor der Prüfung umformt.
Das Zustell-Log zeigt Zugestellt, aber der Chat bleibt leer Der Chat weist die Nachricht ab. Die Antwort des Chats protokollieren. Slack antwortet bei falschem Format mit invalid_payload, ein Teams-Ablauf zeigt den gescheiterten Lauf in seinem Verlauf.
Der Chat zeigt die Kennung statt des Namens GET /v1/players/{id} scheitert: Der Schlüssel fehlt, ist abgelaufen oder hat kein fleet.player.read. Einen Schlüssel der Klasse Nur Lesen verwenden.
Nach fünf Minuten kommt keine Zustellung Der Endpunkt hat das Ereignis nicht abonniert, oder das Display war schon offline. Abonnierte Ereignisse prüfen, siehe Wenn keine Zustellung ankommt.