Transport: WebSocket, HTTP-Rückfall und Wiederverbinden
Wie das Widget mit dem Chat-Server spricht – Adressen, Subprotokoll rc.v1, Rahmenformat, Fehler- und Close-Codes, Grenzen, Wiederverbinden mit seq und Deduplizierung sowie der HTTP-Rückfall über Server-Sent Events und Long-Polling.
Diese Seite beschreibt, was zwischen dem Widget im Browser und dem Chat-Server passiert. Du brauchst sie zur Fehlersuche (Netzwerk-Reiter der Entwicklerwerkzeuge), für Proxy- und Firewall-Freigaben und für deine Content-Security-Policy.
Wichtig: Das Wire-Protokoll ist die Schnittstelle zwischen unserem Widget und unserem Server. Eigene Clients, die das Protokoll direkt sprechen, sind nicht offiziell unterstützt; Felder können sich innerhalb der Protokollversion 1 erweitern. Für eigene Oberflächen nutze die JavaScript-API, für Server-Anbindungen Webhooks und die Server-API.
Adressen
- WebSocket:
wss://ws.reactive.chat/rc_wss/ - HTTP-Rückfall, Offline-Formular, Uploads: dieselbe Adresse mit
https://, alsohttps://ws.reactive.chat/rc_wss/h/…,https://ws.reactive.chat/api/offline/<SITE_KEY>undhttps://ws.reactive.chat/rc_wss/upload/… - Widget-Dateien:
https://cdn.reactive.chat/
Wie das Widget die WebSocket-Adresse findet:
data-rc-ws-urlam Loader-Tag,window.ReactiveChatSettings.wsUrloderReactiveChat('boot', { wsUrl })– wenn gesetzt, gilt dieser Wert.- Sonst wird sie aus dem Ort von
loader.jsabgeleitet:wss://ws.+ die letzten zwei Labels der Domain +/rc_wss/. Auscdn.reactive.chatwirdwss://ws.reactive.chat/rc_wss/.
Daraus folgen zwei Fallen:
- Lieferst du
loader.jsvon deiner eigenen Domain aus, leitet das Widget eine Adresse unter deiner Domain ab. Setze danndata-rc-ws-url(siehe Schnellstart). - Bei zweiteiligen Endungen wie
.co.ukgreift die Ableitung falsch. Setzedata-rc-ws-urlausdrücklich.
Query-Parameter wertet die WebSocket-Strecke nicht aus.
Verbindungsaufbau
- Zeitpunkt: Das Widget verbindet nicht schon beim Laden der Seite, sondern bei der ersten echten Bedienung (Mausbewegung, Klick, Tastendruck, Mausrad, Berührung, Fokus). Sofort verbindet es, wenn das Chatfenster offen ist oder geöffnet wird, ein Gespräch des Besuchers bekannt ist oder noch ungesendete Nachrichten warten. Mit
data-rc-connect="load"an einem beliebigen Element (meist am Loader-Tag) oderwindow.ReactiveChatSettings.connect = 'load'verbindet es wie früher beim Laden. Regeln, die eine bestimmte Zeit nach dem Seitenaufruf auslösen, zählen ab der ersten Bedienung. - Subprotokoll: Das Widget öffnet
new WebSocket(url, 'rc.v1'), der Server spiegeltrc.v1. Die Protokollversion steht nicht in der Adresse, sondern in jedem Rahmen (v: 1). - Nur Textrahmen in UTF-8, ein Rahmen = eine Nachricht. Keine Kompression (
permessage-deflateist serverseitig aus). - Handschlag: Nach
opensendet das Widget spätestens nach 5 Sekundeninit; der Server antwortet mitinit_ok. Kommtopennicht binnen 8 Sekunden, gilt der Versuch als gescheitert; kommtinit_oknicht binnen 10 Sekunden, baut das Widget neu auf. - Herkunftsprüfung: Der Server vergleicht den Hostnamen des
Origin-Kopfes mit den Domains des Projekts (Erlaubte Herkünfte im Konto). Schema und Port zählen nicht; Subdomains passen (www.deine-firma.depasst aufdeine-firma.de,nichtdeine-firma.denicht). Bei Fehlschlag: Fehlerorigin_denied, danach Close 1008, kein Neuversuch. Ist im Projekt keine Domain eingetragen, findet keine Herkunftsprüfung statt – trag deine Domains deshalb immer ein.
Rahmenformat
Jede Nachricht in beide Richtungen hat diese Form:
{ "v": 1, "t": "msg", "id": "01JAB3C4D5E6F7G8H9JKMNPQRS", "ts": 1792500000000, "d": { } }
- v (Ganzzahl, Pflicht) – Protokollversion, heute
1. - t (String, Pflicht) – Nachrichtentyp,
[a-z0-9_]{1,24}. - id (String, Pflicht) – ULID: 26 Zeichen Crockford-Base32, nur Großbuchstaben. Sie ist zugleich der Schlüssel für die Deduplizierung.
- ts (Ganzzahl, Pflicht) – Millisekunden seit 1970, nur zur Diagnose.
- d (Objekt, Pflicht) – Nutzdaten, nie
null, leer als{}.
Unbekannte Typen und unbekannte Felder werden still ignoriert – so bleiben ältere Widgets mit neueren Servern verträglich.
Nachrichtentypen im Überblick
Vom Widget zum Server:
- init – Anmeldung der Verbindung: Site-Key, Sitzungs-, Besucher-, Tab- und Gesprächskennung, Sprache, Einwilligungsstand, ggf. die signierte Identität (
user), Seitenkontext (ctx) und Wiederaufnahmepunkt (resume). - msg – eine Besuchernachricht (Text, bis zu 5 Anhänge, Antwort auf Schnellantworten oder Formulare).
- typing – Tippt-Anzeige (
start/stop). Der Inhalt des Eingabefelds wird nie übertragen. - read – gelesen bis
seq. - ctx – Kontextänderung, z. B. bei Navigation in einer SPA oder nach
identify. - csat – Bewertung (Daumen hoch/runter, Kommentar bis 1000 Zeichen).
- upload_req – Bitte um Upload-Freigabe (Name, MIME-Typ, Größe, SHA-256).
- transcript, forget, consent – Verlauf per E-Mail, eigene Daten löschen, Einwilligung erteilen/widerrufen.
- ping – Lebenszeichen.
Vom Server zum Widget:
- init_ok – bestätigte Kennungen, Verlauf, Gestaltung, Funktionen und Grenzen (siehe unten), Ergebnis der Identitätsprüfung (
identity.verified). - msg – Nachrichten (auch das Echo der eigenen Nachricht mit
client_id). - bot_typing, bot_stream – der KI-Assistent denkt bzw. schreibt;
bot_streamüberträgt immer den ganzen bisher sichtbaren Text, höchstens alle 150 ms. - typing, read, handoff, queue_pos, proactive, conv_list, error, pong.
Fehlercodes
Ein Fehler kommt als Rahmen t: "error" mit d.code, d.message (englisch, nur für Entwickler), d.retry (no, immediate, backoff, after_reinit) und bei Bedarf d.retry_after_ms und d.detail. Die Liste ist geschlossen; ein unbekannter Code wird wie internal behandelt.
- bad_json – kein gültiges JSON; nach drei Fällen Close 1008.
- bad_envelope – Rahmen verletzt das Format (auch: ULID mehr als 24 h daneben,
detail.reason: stale_or_future). - unsupported_version – Version nicht verhandelbar; Close 4001.
- unknown_site_key – Site-Key unbekannt, Projekt pausiert oder archiviert; Close 4002.
- origin_denied – Herkunft nicht in den Domains des Projekts; Close 1008.
- visitor_blocked – Besucher gesperrt; Close 1008.
- auth_failed – Identitätsprüfung gescheitert,
detail.reason:no_hash,bad_hash,bad_exp,expired,exp_too_far. Die Sitzung läuft anonym weiter. - not_initialized / already_initialized – Nachricht vor bzw. zweites
init. - init_timeout – kein
initbinnen 5 s; Close 4004. - conversation_not_found / conversation_closed
- rate_limited – Ratenbegrenzung; mit
retry_after_ms. Wiederholt sich das auf derselben Verbindung, schließt der Server mit 4008. - payload_too_large – Rahmen über 64 KiB.
- msg_too_long – Text länger als die Tarifgrenze,
detail.max. - unsupported_kind, attachment_invalid
- upload_disabled, upload_too_large, upload_mime_denied, upload_expired
- consent_required – die Aktion braucht eine Einwilligung.
- dnd_active – „Nicht stören“ ist aktiv.
- session_conflict – zu viele Verbindungen derselben Sitzung (mehr als 3); Close 4003, der Tab wird zum Mitläufer.
- internal, unavailable – vorübergehend; Neuversuch mit Wartezeit.
Close-Codes
- 1000, 1001, 1006, 1011, 1013, 4004 – das Widget verbindet neu (mit Wartezeit).
- 1008 – Herkunft abgewiesen, wiederholt ungültiges JSON, Sperre durch den Missbrauchsschutz. Kein Neuversuch auf dieser Seite.
- 4001 – Version nicht verhandelbar. Kein Neuversuch.
- 4002 – Site-Key unbekannt oder Projekt inaktiv. Kein Neuversuch.
- 4008 – wiederholte Ratenbegrenzung. Das Widget wartet
retry_after_msab und verbindet neu. - 4003 – Sitzungskonflikt; der Tab läuft als Mitläufer eines anderen Tabs.
Grenzen je Verbindung
Die verbindlichen Werte schickt der Server in init_ok.limits und init_ok.features:
- heartbeat_ms 25 000 – das Widget sendet 25 s nach der letzten ausgehenden Nachricht ein
ping. - pong_timeout_ms 10 000 – zwei ausbleibende
pongführen zum Neuaufbau. - idle_timeout_ms 70 000 – der Server schließt nach 70 s ohne eingehenden Rahmen (1001).
- max_frame_bytes 65 536 – größere Rahmen:
payload_too_large. - msg_per_10s 20 und frames_per_10s 120 – Token-Eimer je Verbindung; darüber
rate_limited. - reconnect_cap_ms 30 000 – größte Wartezeit zwischen Neuversuchen.
- dedup_window_ms 86 400 000 – 24 Stunden Deduplizierung.
- features.max_msg_len – Zeichen (Unicode-Codepoints) je Besuchernachricht: 1000 im Tarif Free, 4000 in bezahlten Tarifen. Das Widget sendet zu lange Texte nicht.
- features.max_upload_bytes, features.allowed_mime – siehe Grenzen und Quoten.
Wiederverbinden, Reihenfolge, Duplikate
- Wartezeit:
min(30000, 500 × 2^(Versuch−1))ms, davon zufällig die obere Hälfte (Versuch 1: 250–500 ms, ab Versuch 7: 15–30 s). In Hintergrund-Tabs dreifach. Einretry_after_msdes Servers ist die Untergrenze. Die Zahl der Versuche ist unbegrenzt; der Zähler fällt erst beiinit_okzurück. Meldet der Browseronline, versucht es das Widget sofort; ist er offline, wartet es. - Reihenfolge: Jede Nachricht eines Gesprächs trägt eine lückenlose
seqab 1. Angezeigt wird nachseq, nicht nach Zeit. Beim Neuaufbau schickt das Widgetresume.last_seqund bis zu 50 offene Kennungen; der Server liefert alles danach nach (höchstens 100 Nachrichten, sonsthas_more) und wiederholt die Bestätigungen für bereits angekommene Nachrichten. - Keine Doppelten: Der Server merkt sich jede Nachrichten-
id24 Stunden. Ein Wiedergänger wird nicht erneut abgelegt, sondern bekommt dieselbe Bestätigung. „Erneut senden“ im Widget verwendet dieselbeid. - Zustände einer ausgehenden Nachricht:
pending→sent→delivered(Echo vom Server) →read;failednach 5 Versuchen, nach 24 h oder beiretry: "no". - Offline-Puffer: Bricht die Verbindung ab, landen Nachrichten in einem Puffer (höchstens 50 Einträge, 128 KiB, 24 h) – mit Einwilligung im
localStorage, ohne imsessionStorage– und gehen beim nächsteninit_okhinaus. - Mehrere Tabs, eine Verbindung: Die Tabs einer Website wählen einen Leader (Web Locks, sonst ein Lebenszeichen im
localStorage), nur er hält die Verbindung; die übrigen sprechen überBroadcastChannel.
HTTP-Rückfall
Lässt ein Netz keinen WebSocket durch, läuft der Chat über gewöhnliche HTTPS-Anfragen weiter – mit denselben Rahmen und denselben Grenzen. Nur das Widget nutzt ihn; die Agentenoberfläche verbindet sich immer per WebSocket. Die Kurzfassung für Redakteure steht unter HTTP-Rückfall.
Wann er greift:
- der Browser kennt keinen WebSocket;
- zwei WebSocket-Aufbauten scheitern nacheinander, bevor auf dieser Seite je ein WebSocket zustande kam (ein Versuch ohne Ergebnis nach 8 Sekunden zählt als gescheitert);
- im
sessionStoragesteht ein Eintragrc_http, der jünger als 30 Minuten ist (Folgeseiten im selben Tab); - du erzwingst ihn mit
data-rc-transport="http"(an irgendeinem Element, das beim Start im Dokument steht) oder mitwindow.ReactiveChatSettings = { transport: 'http' }vor dem Loader.ReactiveChat('boot', { transport: 'http' })wirkt nicht – die Verbindungsart steht beim Start fest.
Ohne fetch im Browser gibt es keinen Rückfall. Bricht eine bestehende WebSocket-Verbindung ab, verbindet das Widget wieder per WebSocket. Nach einem Rückfall prüft es einmal je Seite still, ob ein WebSocket inzwischen durchkommt; gelingt das, löscht es rc_http, und die nächste Seite nimmt wieder den WebSocket.
Die Anfragen (unter https://ws.reactive.chat/rc_wss/h/):
- POST h/auf – öffnet die Verbindung. Antwort
{"v":1,"cid":"…","tok":"…","poll_ms":25000,"idle_ms":60000}.tokist ein kurzlebiges Verbindungstoken (32 Zeichen). - GET h/<cid>/sse?tok=… – Server-Sent Events, je Rahmen
id:unddata:, alle 15 s ein Lebenszeichen. Nachliefern überLast-Event-ID. - GET h/<cid>/poll?tok=…&nach=<n> – Long-Polling; der Server hält bis zu 25 s offen und antwortet
{"v":1,"n":…,"rahmen":[…]}. - POST h/<cid>/tx?tok=… – senden,
text/plain, ein Rahmen oder ein Array mit höchstens 20 Rahmen; Antwort 204. - POST h/<cid>/zu?tok=… – schließen (per
sendBeacon).
Das Widget versucht zuerst SSE; kommt binnen 5 Sekunden kein erster Rahmen (etwa weil ein Proxy puffert), wechselt es auf Long-Polling. Fehler wie 404 (unbekannt), 403, 413 oder 429 behandelt es wie einen Verbindungsabbruch und versucht es mit Wartezeit erneut. Nach 60 Sekunden ohne Abruf schließt der Server die Verbindung. CORS: Access-Control-Allow-Origin spiegelt die Herkunft, ohne Credentials; die Herkunftsprüfung findet wie beim WebSocket im init statt.
Voraussetzungen auf deiner Seite:
connect-srcdeiner CSP enthält die Chat-Adresse mitwss://undhttps://.- Firewalls und Proxys lassen Anfragen von mindestens 30 Sekunden Dauer zu.
- Ein eigener Proxy reicht
/rc_wss/ungepuffert durch (nginxproxy_buffering offundproxy_cache off, Apacheflushpackets=on, Caddyflush_interval -1) und schreibt/rc_wss/h/nicht ins Zugriffsprotokoll (Token in der Query). Siehe Eigene Domain.
Im Browser gespeichert: nur der Eintrag rc_http im sessionStorage mit dem Zeitpunkt des HTTPS-Aufbaus, ohne personenbezogene Daten.
Welcher Weg aktiv ist
Im Netzwerk-Reiter der Entwicklerwerkzeuge:
- WebSocket: eine Verbindung vom Typ WebSocket zu
wss://ws.reactive.chat/rc_wss/(bzw. deiner Subdomain). In den Rahmen siehst duinit,init_ok,ping/pongund die Nachrichten. - HTTP-Rückfall: zuerst
h/auf, dann ein lang laufendes…/sse(oder wiederkehrende…/poll) und für jede gesendete Nachricht ein…/tx.
Weiter: Grenzen und Quoten · Fehlerbehebung
Stand: