Transport : WebSocket, repli HTTP et reconnexion
Les échanges entre le widget et le serveur de chat : adresses, sous-protocole rc.v1, format des trames, codes d’erreur et de fermeture, limites, reconnexion avec seq et déduplication, repli HTTP via SSE et interrogation longue.
Cette page décrit les échanges entre le widget du navigateur et le serveur de chat. Elle sert au dépannage (onglet Réseau des outils de développement), aux règles de proxy et de pare-feu et à votre Content Security Policy.
Important : le protocole de communication est l’interface entre notre widget et notre serveur. Les clients personnalisés utilisant directement ce protocole ne sont pas officiellement pris en charge ; des champs peuvent être ajoutés dans la version 1. Pour vos propres interfaces, utilisez l’API JavaScript ; pour les connexions serveur, les webhooks et l’API serveur.
Adresses
- WebSocket :
wss://ws.reactive.chat/rc_wss/ - Repli HTTP, formulaire hors ligne, envois de fichiers : la même adresse avec
https://, soithttps://ws.reactive.chat/rc_wss/h/…,https://ws.reactive.chat/api/offline/<SITE_KEY>ethttps://ws.reactive.chat/rc_wss/upload/… - Fichiers du widget :
https://cdn.reactive.chat/
Comment le widget trouve l’adresse WebSocket :
data-rc-ws-urlsur la balise du chargeur,window.ReactiveChatSettings.wsUrlouReactiveChat('boot', { wsUrl }): si définie, cette valeur s’applique.- Sinon, elle est déduite de l’emplacement de
loader.js:wss://ws.+ les deux derniers segments du domaine +/rc_wss/.cdn.reactive.chatdevientwss://ws.reactive.chat/rc_wss/.
Deux pièges en découlent :
- Si vous distribuez
loader.jsdepuis votre domaine, le widget déduit une adresse sous votre domaine. Définissez alorsdata-rc-ws-url(voir Démarrage rapide). - Avec les terminaisons en deux parties comme
.co.uk, la déduction est incorrecte. Définissez explicitementdata-rc-ws-url.
La route WebSocket n’évalue aucun paramètre de requête.
Établissement de la connexion
- Moment : le widget ne se connecte pas pendant le chargement de la page, mais à la première interaction réelle (mouvement de souris, clic, touche, molette, toucher, focus). Il se connecte immédiatement si la fenêtre du chat est ouverte ou en cours d’ouverture, si une conversation du visiteur est connue ou si des messages non envoyés attendent. Avec
data-rc-connect="load"sur un élément (généralement la balise du chargeur) ouwindow.ReactiveChatSettings.connect = 'load', il se connecte au chargement comme auparavant. Les règles déclenchées après une durée depuis la consultation de la page comptent à partir de la première interaction. - Sous-protocole : le widget ouvre
new WebSocket(url, 'rc.v1')et le serveur renvoierc.v1. La version du protocole ne figure pas dans l’adresse, mais dans chaque trame (v: 1). - Trames textuelles uniquement, en UTF-8 ; une trame = un message. Aucune compression (
permessage-deflateest désactivé côté serveur). - Négociation : après
open, le widget envoieiniten 5 secondes maximum ; le serveur répond avecinit_ok. Siopenn’arrive pas en 8 secondes, la tentative est considérée comme échouée ; siinit_okn’arrive pas en 10 secondes, le widget se reconnecte. - Vérification de l’origine : le serveur compare le nom d’hôte de l’en-tête
Originaux domaines du projet (Origines autorisées dans votre compte). Le schéma et le port ne comptent pas ; les sous-domaines correspondent (www.your-company.comcorrespond àyour-company.com, mais pasnotyour-company.com). En cas d’échec : erreurorigin_denied, puis fermeture 1008, sans nouvelle tentative. Si aucun domaine n’est saisi dans le projet, aucune vérification de l’origine n’a lieu ; saisissez donc toujours vos domaines.
Format des trames
Chaque message, dans les deux sens, a cette forme :
{ "v": 1, "t": "msg", "id": "01JAB3C4D5E6F7G8H9JKMNPQRS", "ts": 1792500000000, "d": { } }
- v (entier, obligatoire) – version du protocole, actuellement
1. - t (chaîne, obligatoire) – type de message,
[a-z0-9_]{1,24}. - id (chaîne, obligatoire) – ULID : 26 caractères de Crockford Base32, uniquement en majuscules. C’est aussi la clé de déduplication.
- ts (entier, obligatoire) – millisecondes depuis 1970, uniquement pour le diagnostic.
- d (objet, obligatoire) – données transmises, jamais
null,{}si vide.
Les types et champs inconnus sont ignorés silencieusement ; les anciens widgets restent ainsi compatibles avec les nouveaux serveurs.
Vue d’ensemble des types de messages
Du widget vers le serveur :
- init – enregistrement de la connexion : clé de site, identifiants de session, visiteur, onglet et conversation, langue, état du consentement, éventuelle identité signée (
user), contexte de page (ctx) et point de reprise (resume). - msg – message d’un visiteur (texte, jusqu’à 5 pièces jointes, réponse rapide ou réponse à un formulaire).
- typing – indicateur de saisie (
start/stop). Le contenu du champ de saisie n’est jamais transmis. - read – lu jusqu’à
seq. - ctx – changement de contexte, par exemple lors d’une navigation dans une SPA ou après
identify. - csat – évaluation (pouce levé/baissé, commentaire jusqu’à 1000 caractères).
- upload_req – demande d’autorisation d’envoi de fichier (nom, type MIME, taille, SHA-256).
- transcript, forget, consent – conversation par e-mail, suppression de ses propres données, octroi/retrait du consentement.
- ping – maintien de connexion.
Du serveur vers le widget :
- init_ok – identifiants confirmés, historique, design, fonctions et limites (voir ci-dessous), résultat de la vérification d’identité (
identity.verified). - msg – messages (y compris l’écho du message du visiteur avec
client_id). - bot_typing, bot_stream – l’assistant IA réfléchit ou écrit ;
bot_streamtransmet toujours tout le texte visible jusqu’alors, au plus toutes les 150 ms. - typing, read, handoff, queue_pos, proactive, conv_list, error, pong.
Codes d’erreur
Une erreur arrive comme trame t: "error" avec d.code, d.message (en anglais, pour les développeurs uniquement), d.retry (no, immediate, backoff, after_reinit) et, si nécessaire, d.retry_after_ms et d.detail. La liste est fermée ; un code inconnu est traité comme internal.
- bad_json – JSON invalide ; après trois occurrences, fermeture 1008.
- bad_envelope – la trame ne respecte pas le format (également : décalage de l’ULID supérieur à 24 h,
detail.reason: stale_or_future). - unsupported_version – impossible de négocier la version ; fermeture 4001.
- unknown_site_key – clé de site inconnue, projet en pause ou archivé ; fermeture 4002.
- origin_denied – origine absente des domaines du projet ; fermeture 1008.
- visitor_blocked – visiteur bloqué ; fermeture 1008.
- auth_failed – vérification d’identité échouée,
detail.reason:no_hash,bad_hash,bad_exp,expired,exp_too_far. La session continue anonymement. - not_initialized / already_initialized – message avant
initou secondinit. - init_timeout – aucun
initen 5 s ; fermeture 4004. - conversation_not_found / conversation_closed
- rate_limited – limite de fréquence, avec
retry_after_ms. Si cela se répète sur la même connexion, le serveur ferme avec 4008. - payload_too_large – trame dépassant 64 KiB.
- msg_too_long – texte dépassant la limite du forfait,
detail.max. - unsupported_kind, attachment_invalid
- upload_disabled, upload_too_large, upload_mime_denied, upload_expired
- consent_required – l’action exige un consentement.
- dnd_active – « Ne pas déranger » est actif.
- session_conflict – trop de connexions de la même session (plus de 3) ; fermeture 4003, l’onglet devient suiveur.
- internal, unavailable – temporaire ; réessayer après un délai.
Codes de fermeture
- 1000, 1001, 1006, 1011, 1013, 4004 – le widget se reconnecte (après un délai).
- 1008 – origine refusée, JSON invalide répété, blocage par la protection contre les abus. Aucune nouvelle tentative sur cette page.
- 4001 – impossible de négocier la version. Aucune nouvelle tentative.
- 4002 – clé de site inconnue ou projet inactif. Aucune nouvelle tentative.
- 4008 – limitation de fréquence répétée. Le widget attend
retry_after_mset se reconnecte. - 4003 – conflit de session ; l’onglet fonctionne comme suiveur d’un autre onglet.
Limites par connexion
Le serveur envoie les valeurs applicables dans init_ok.limits et init_ok.features :
- heartbeat_ms 25 000 – le widget envoie un
ping25 s après le dernier message sortant. - pong_timeout_ms 10 000 – deux
pongmanquants entraînent une reconnexion. - idle_timeout_ms 70 000 – le serveur ferme après 70 s sans trame entrante (1001).
- max_frame_bytes 65 536 – trames plus grandes :
payload_too_large. - msg_per_10s 20 et frames_per_10s 120 – seau à jetons par connexion ; au-delà,
rate_limited. - reconnect_cap_ms 30 000 – délai maximal entre les tentatives.
- dedup_window_ms 86 400 000 – 24 heures de déduplication.
- features.max_msg_len – caractères (points de code Unicode) par message de visiteur : 1000 avec le forfait Free, 4000 avec les forfaits payants. Le widget n’envoie pas les textes trop longs.
- features.max_upload_bytes, features.allowed_mime – voir Limites et quotas.
Reconnexion, ordre, doublons
- Délai :
min(30000, 500 × 2^(attempt−1))ms, dont une valeur aléatoire dans la moitié supérieure est utilisée (tentative 1 : 250–500 ms, à partir de la tentative 7 : 15–30 s). Trois fois plus long dans les onglets en arrière-plan. Unretry_after_msdu serveur constitue la borne inférieure. Le nombre de tentatives est illimité ; le compteur n’est réinitialisé que surinit_ok. Si le navigateur signaleonline, le widget réessaie immédiatement ; hors ligne, il attend. - Ordre : chaque message d’une conversation porte un
seqsans interruption, à partir de 1. Les messages sont affichés selonseq, pas selon l’heure. À la reconnexion, le widget envoieresume.last_seqet jusqu’à 50 identifiants en attente ; le serveur livre tout ce qui suit (au maximum 100 messages, sinonhas_more) et répète les confirmations des messages déjà arrivés. - Aucun doublon : le serveur mémorise chaque
idde message pendant 24 heures. Un message répété n’est pas stocké à nouveau mais reçoit la même confirmation. « Renvoyer » dans le widget utilise le mêmeid. - États d’un message sortant :
pending→sent→delivered(écho du serveur) →read;failedaprès 5 tentatives, après 24 h ou avecretry: "no". - Tampon hors ligne : si la connexion tombe, les messages entrent dans un tampon (au maximum 50 entrées, 128 KiB, 24 h), dans
localStorageavec consentement, danssessionStoragesans consentement, puis partent au prochaininit_ok. - Plusieurs onglets, une connexion : les onglets d’un site élisent un chef de file (Web Locks, sinon un signal de maintien dans
localStorage) ; seul celui-ci garde la connexion, les autres communiquent viaBroadcastChannel.
Repli HTTP
Si un réseau ne laisse pas passer WebSocket, le chat continue via des requêtes HTTPS ordinaires, avec les mêmes trames et limites. Seul le widget l’utilise ; l’interface agents se connecte toujours via WebSocket. La version courte destinée aux rédacteurs figure dans Repli HTTP.
Quand il s’applique :
- le navigateur ne prend pas en charge WebSocket ;
- deux tentatives WebSocket échouent à la suite avant qu’une connexion WebSocket ait été établie sur cette page (une tentative sans résultat après 8 secondes est considérée comme échouée) ;
sessionStoragecontient une entréerc_httpdatant de moins de 30 minutes (pages suivantes du même onglet) ;- vous le forcez avec
data-rc-transport="http"(sur tout élément présent dans le document au démarrage) ou avecwindow.ReactiveChatSettings = { transport: 'http' }avant le chargeur.ReactiveChat('boot', { transport: 'http' })est sans effet ; le type de connexion est fixé au démarrage.
Sans fetch dans le navigateur, aucun repli n’est possible. Si une connexion WebSocket existante tombe, le widget se reconnecte via WebSocket. Après un repli, il vérifie discrètement une fois par page si WebSocket passe désormais ; si oui, il supprime rc_http et la page suivante utilise à nouveau WebSocket.
Les requêtes (sous https://ws.reactive.chat/rc_wss/h/) :
- POST h/auf – ouvre la connexion. Réponse
{"v":1,"cid":"…","tok":"…","poll_ms":25000,"idle_ms":60000}.tokest un jeton de connexion de courte durée (32 caractères). - GET h/<cid>/sse?tok=… – Server-Sent Events,
id:etdata:par trame, maintien de connexion toutes les 15 s. Les trames manquées sont livrées viaLast-Event-ID. - GET h/<cid>/poll?tok=…&nach=<n> – interrogation longue ; le serveur garde la requête ouverte jusqu’à 25 s et répond avec
{"v":1,"n":…,"rahmen":[…]}. - POST h/<cid>/tx?tok=… – envoi,
text/plain, une trame ou un tableau de 20 trames maximum ; réponse 204. - POST h/<cid>/zu?tok=… – ferme (via
sendBeacon).
Le widget essaie SSE en premier ; si aucune première trame n’arrive en 5 secondes (par exemple à cause d’un proxy avec mise en tampon), il passe à l’interrogation longue. Il traite les erreurs comme 404 (unbekannt), 403, 413 ou 429 comme une connexion interrompue et réessaie après un délai. Après 60 secondes sans requête, le serveur ferme la connexion. CORS : Access-Control-Allow-Origin reprend l’origine, sans identifiants d’accès ; la vérification de l’origine a lieu dans init, comme pour WebSocket.
Exigences de votre côté :
- Le
connect-srcde votre CSP contient l’adresse du chat avecwss://ethttps://. - Les pare-feu et proxys autorisent des requêtes durant au moins 30 secondes.
- Votre proxy transmet
/rc_wss/sans mise en tampon (nginxproxy_buffering offetproxy_cache off, Apacheflushpackets=on, Caddyflush_interval -1) et n’écrit pas/rc_wss/h/dans le journal d’accès (jeton dans les paramètres de requête). Voir Domaine personnalisé.
Stockage dans le navigateur : uniquement l’entrée rc_http dans sessionStorage avec l’heure de la connexion HTTPS, sans données personnelles.
Quelle route est active
Dans l’onglet Réseau des outils de développement :
- WebSocket : une connexion de type WebSocket à
wss://ws.reactive.chat/rc_wss/(ou votre sous-domaine). Les trames montrentinit,init_ok,ping/ponget les messages. - Repli HTTP : d’abord
h/auf, puis une requête longue…/sse(ou des requêtes récurrentes…/poll) et une…/txpour chaque message envoyé.
Suite : Limites et quotas · Dépannage
Dernière mise à jour: