Webhooks: Ereignisse an deinen Server
Signierte Webhooks und REST-Hooks von reactive.chat – Ereignisse, Kopfzeilen, Rumpf, Signaturprüfung mit HMAC-SHA256, Wiederholungen, Zeitlimits, Deaktivierung und Beispiele zum Prüfen in PHP, Node und Python.
Webhooks melden deinem Server, was im Chat passiert: ein neues Gespräch, eine neue Nachricht, eine Übergabe an das Team, eine Bewertung. Es gibt zwei Wege:
- Signierter Webhook – eine Zieladresse je Workspace, im Konto eingerichtet, jede Zustellung mit HMAC-SHA256 signiert. Der richtige Weg für deine eigenen Systeme.
- REST-Hooks – Ziele, die ein Automatisierungswerkzeug (Zapier, Make, n8n) über die Server-API selbst an- und abmeldet.
Ereignisse
- conversation.created – ein Gespräch ist entstanden. Kanal in
channel(web,whatsapp,email…). - message.created – eine Nachricht wurde abgelegt (von Besucher, Assistent, Agent oder System).
- handoff.requested – das Gespräch soll an einen Menschen gehen; mit
reasonundtarget. - csat.submitted – der Besucher hat bewertet;
score(1oder-1) undcomment(mit maskierten persönlichen Daten). - fehlerfall.erstellt – ein neuer JavaScript-Fehlerfall auf deiner Website wurde erkannt (siehe Handbuch).
Gespräche aus der Website-Suche (Smart Answer) lösen erst dann Ereignisse aus, wenn sie als Chat sichtbar werden.
Signierten Webhook einrichten
Im Konto unter Einstellungen → Integrationen → Webhooks (Recht: Integrationen verwalten):
- URL –
https://, höchstens 1000 Zeichen, öffentlich erreichbar. Eine Adresse je Workspace. - Ereignisse – Auswahl; keine Auswahl heißt alle.
- Signaturgeheimnis – wird auf der Seite angezeigt (64 Hex-Zeichen). Neu erzeugen macht das bisherige sofort ungültig; plane den Wechsel deshalb so, dass dein Empfänger kurz beide akzeptiert.
Beim Speichern wird kein Testaufruf geschickt. Nach dem Einschalten können bis zu etwa 15 Sekunden vergehen, bis Ereignisse fließen; Ereignisse von vor dem Einschalten werden nicht nachgeliefert.
Die Zustellung
POST /rc-webhook HTTP/1.1
Host: hooks.deine-firma.de
Content-Type: application/json; charset=utf-8
User-Agent: reactive.chat-webhook/1.0
X-RC-Event: csat.submitted
X-RC-Delivery: 01JAB3C4D5E6F7G8H9JKMNPQRS
X-RC-Timestamp: 1792503600
X-RC-Signature: sha256=3f0a1c...
{"id":"01JAB3C4D5E6F7G8H9JKMNPQRS","event":"csat.submitted","ts":1792503600,"site_key":"ABCDEFGHJKLMNPQRSTUVWX12","conversation_id":"01JAB2...","session_id":"s_...","visitor_id":"v_...","data":{"score":5,"comment":"Sehr hilfreich"}}
Kopfzeilen:
- X-RC-Event – Name des Ereignisses.
- X-RC-Delivery – Kennung der Zustellung; bleibt bei Wiederholungen gleich. Nutze sie, um Doppelte zu erkennen.
- X-RC-Timestamp – Unix-Sekunden.
- X-RC-Signature –
sha256=+hex(HMAC_SHA256(geheimnis, X-RC-Timestamp + "." + roher_body)). Schlüssel ist die angezeigte Hex-Zeichenkette selbst.
Rumpf:
- id (String) – wie
X-RC-Delivery. - event (String).
- ts (Ganzzahl) – Unix-Sekunden.
- site_key, conversation_id, session_id, visitor_id (String oder
null). - data (Objekt) – ereignisabhängige Daten. Bei
fehlerfall.erstellt:fall_id,project_id,signatur,meldung,datei,zeile,spalte,seite,browser,os,stack,erste_ms,url(Link zum Fall im Konto). Beimessage.created:messagemitid,seq,kind,direction,author_type,author_name(nur bei Agent und Assistent),via,body(serverseitig maskiert),redacted,ts(Millisekunden). Beihandoff.requested:reasonundtarget. Beicsat.submitted:scoreundcomment. Beiconversation.created:channel.
Signatur prüfen
Prüfe vor jeder Verarbeitung: Zeitstempel höchstens 300 Sekunden alt, Signatur über den rohen Body, Vergleich zeitkonstant.
PHP:
<?php
$geheim = getenv('RC_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_RC_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_RC_SIGNATURE'] ?? '';
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }
$soll = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $geheim);
if (!hash_equals($soll, $sig)) { http_response_code(401); exit; }
$ereignis = json_decode($body, true);
// Doppelte erkennen: $ereignis['id'] merken
http_response_code(204);
Node.js (Express):
const crypto = require('crypto');
app.post('/rc-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-RC-Timestamp') || '';
const sig = req.get('X-RC-Signature') || '';
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const soll = 'sha256=' + crypto.createHmac('sha256', process.env.RC_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8')).digest('hex');
if (sig.length !== soll.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(soll))) {
return res.sendStatus(401);
}
const ereignis = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204);
verarbeiteSpaeter(ereignis); // schnell antworten, danach arbeiten
});
Python (Flask):
import hmac, hashlib, os, time
from flask import request, abort
@app.post("/rc-webhook")
def rc_webhook():
ts = request.headers.get("X-RC-Timestamp", "")
sig = request.headers.get("X-RC-Signature", "")
body = request.get_data() # rohe Bytes
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
soll = "sha256=" + hmac.new(os.environ["RC_WEBHOOK_SECRET"].encode(),
ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(soll, sig):
abort(401)
return "", 204
Antworten, Wiederholungen, Deaktivierung
Antworte schnell mit einem 2xx und verarbeite danach. Zeitlimits: 4 Sekunden für den Verbindungsaufbau, 10 Sekunden gesamt. Weiterleitungen werden nicht verfolgt.
- 2xx – zugestellt.
- 408, 429, 5xx, Zeitüberschreitung, Netzfehler – Wiederholung nach 1 s, 5 s, 25 s, 125 s und 625 s (bis zu sechs Versuche insgesamt), danach verworfen.
- 410 Gone – die Integration wird deaktiviert. Nutze 410 nur, wenn du wirklich keine Webhooks mehr willst.
- andere 4xx – keine Wiederholung.
Ereignisse, die älter als eine Stunde sind, werden nicht mehr zugestellt. Häufen sich Fehler (5 in 60 Sekunden), pausiert die Zustellung an dein Ziel für 30 Sekunden; die Aufträge werden danach wiederholt. Ein Chat wartet nie auf deinen Webhook.
Die Reihenfolge der Zustellungen ist nicht garantiert. Sortiere bei Bedarf nach ts bzw. nach seq der Nachrichten aus der API.
REST-Hooks (Zapier, Make, n8n)
Automatisierungswerkzeuge melden ihre Ziele selbst über die Server-API an. Du kannst das auch direkt nutzen:
curl -X POST https://reactive.chat/v1/hooks \
-H "Authorization: Bearer rc_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"message.created","target_url":"https://hooks.deine-firma.de/rc","source":"n8n"}'
- POST /v1/hooks – Rechtestufe mit Schreibrecht.
event(eines der Ereignisse oben),target_url(https://, höchstens 500 Zeichen, öffentlich),source(zapier,makeodern8n, Vorgabezapier). Antwort 201 mitid. Dieselbe Kombination aus Ereignis und Ziel wird nicht doppelt angelegt. Höchstens 200 Anmeldungen je Workspace. - GET /v1/hooks – Liste mit
id,event,target_url,source,failures,created_at,last_ok_at. - DELETE /v1/hooks/{id} – abmelden.
Zustellung eines REST-Hooks:
POST https://hooks.deine-firma.de/rc
Content-Type: application/json
User-Agent: reactive.chat-n8n/1.0
X-RC-Event: message.created
X-RC-Delivery: 01JAB3...
{"event":"message.created","delivery_id":"01JAB3...","data":{"name":"message.created","ts":1792503600123,"site_key":"...","conversation_id":"...","session_id":"...","visitor_id":"...","message":{"id":"...","seq":7,"kind":"text","direction":"in","author_type":"bot","author_name":"Assistent","via":null,"body":"…","redacted":false,"ts":1792503600120}}}
- Nicht signiert. Verwende eine schwer zu erratende Zieladresse und prüfe
X-RC-Deliveryauf Doppelte. Für eigene Systeme ist der signierte Webhook der bessere Weg. dataist das vollständige Ereignis: zusätzlichworkspace_id,delivery_idundattempt(Zählung der Versuche).data.tsist hier in Millisekunden.datavonmessage.createdenthältmessagemitid,seq,kind,direction,author_type,author_name(nur bei Agent und Assistent),via,body(serverseitig maskiert),redacted,ts.handoff.requestedträgtreasonundtarget,csat.submittedscoreundcomment,conversation.createdchannel.- Ein 410 meldet nur diese eine Anmeldung ab. Hooks eines widerrufenen API-Schlüssels werden nicht mehr bedient.
- Nach aktuellem Stand setzt die Zustellung voraus, dass die jeweilige Integration (Zapier, Make oder n8n) im Workspace unter Integrationen eingeschaltet ist.
Was es nicht gibt
- Keinen eingehenden Webhook, über den dein Server Besuchernachrichten in ein Gespräch einspeist. Antworten im Namen deines Teams sendest du über die Server-API oder MCP.
- Kein Übergangsfenster beim Erneuern des Signaturgeheimnisses.
- Kein Ereignis beim Löschen eines Gesprächs.
Weiter: Server-API
Stand: