Widget — Intégration technique
Guide pour développeurs : script, payload de init(), association et création d'entreprise, cycle de vie, restauration authentifiée (restore_id), CSP et vérification.
Pages de parcours : Vue d'ensemble · B2C · B2B.
Édition de référence : 26 octobre 2026. La restauration entre appareils s'ajoute au payload entreprise. Avant de l'activer, vérifiez que provisionRestoreId est disponible dans votre installation.
L'interface est dans un iframe. Le site hôte n'a pas besoin d'Angular, d'un client Supabase ni d'un accès à la base Quoting24.
Choisir le type d'intégration
| Cas | Implémentation | Continuité |
|---|---|---|
| Site public, visiteur inconnu | Script seul. Formulaire de contact | Session invité dans le même navigateur |
| Site avec les données du client | Script et init() avec profil et entreprise |
Session invité préremplie et association d'entreprise |
| Portail avec historique personnel entre appareils | Aussi un backend pour external_user_id et restore_id |
Historique personnel sur le canal widget autorisé |
Associer une entreprise et restaurer l'historique sont deux opérations distinctes. external_id, company_name, le nom, l'e-mail et le téléphone n'autorisent pas les conversations d'une autre personne. Un utilisateur authentifié sur le site reste un invité du widget tant que vous ne fournissez pas un credential de restauration valide.
1. Configuration depuis Quoting24
Dans Paramètres → Canaux, ouvrez le canal widget et récupérez :
- URL complète du script et clé publique
- domaines autorisés, y compris recettage et développement local
- logo, couleur, position, accueil, bienvenue et horaires
Utilisez l'URL et la clé du même environnement. Dans les exemples, remplacez HOTE_WIDGET et CLE_PUBLIQUE_WIDGET. La clé dans l'URL est publique et sélectionne le canal : ce n'est ni un credential personnel ni un secret d'intégration backend.
Les domaines sont des noms d'hôte (shop.example.com, localhost), sans https://, port, chemin ni préfixe *.. example.com autorise aussi ses sous-domaines. Une liste vide n'applique aucune restriction. Pour l'essai local, servez la page en HTTP, pas comme fichier.
| Valeur | Où la conserver | Rôle |
|---|---|---|
| Clé publique | URL du script | Sélectionne le canal widget |
| Secret privé de l'intégration | Secrets du backend seulement | Autorise la création et la réutilisation de restore_id |
restore_id |
Stockage privé par utilisateur ; dans la page seulement pour une session authentifiée | Indique quel historique restaurer |
| Credential de session invité | localStorage du site, par clé de widget, géré par le loader |
Continuité invité dans le même navigateur |
La restauration exige une intégration backend, un point d'accès privé et un secret, séparés du code public. Voir section 6.
2. Installation
Bouton flottant
Une seule fois, avant </body> :
Le bouton apparaît après le chargement des réglages graphiques. Un nouveau visiteur ouvre le formulaire avec téléphone et nom facultatif. Le loader conserve le credential invité dans localStorage, séparé par clé. Si le stockage est bloqué, la continuité peut se limiter à la page courante.
Le loader expose un unique window.Quoting24Widget. Un second script ne crée pas un second widget.
Conversation intégrée
Conteneur avant le script, hauteur explicite :
<section aria-labelledby="titre-assistance">
<h2 id="titre-assistance">Assistance</h2>
<div id="chat-assistance" style="width: 100%; height: 600px;"></div>
</section>
<script
src="https://HOTE_WIDGET/assets/widget/script.js?key=CLE_PUBLIQUE_WIDGET&mode=embedded&target=chat-assistance"
async
></script>
target est l'ID, sans #. L'iframe remplit le conteneur. Il n'y a pas de bouton flottant. open(), close() et toggle() n'ont pas d'effet.
Attendre le loader
Pour les données préremplies, utilisez ce module à la place de la balise statique, importé une seule fois dans la structure principale. Les exemples sont des modules JavaScript : bundler, ou <script type="module" src="/assets/assistance.js"></script>.
// /assets/quoting24-loader.js
const widgetScriptUrl =
"https://HOTE_WIDGET/assets/widget/script.js?key=CLE_PUBLIQUE_WIDGET";
let widgetPromise;
export function loadWidget() {
if (window.Quoting24Widget) {
return Promise.resolve(window.Quoting24Widget);
}
if (widgetPromise) return widgetPromise;
widgetPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.id = "quoting24-widget-script";
script.src = widgetScriptUrl;
script.async = true;
script.onload = () => {
if (window.Quoting24Widget) resolve(window.Quoting24Widget);
else {
script.remove();
reject(new Error("API du widget indisponible"));
}
};
script.onerror = () => {
script.remove();
reject(new Error("Échec du chargement du widget"));
};
document.body.appendChild(script);
}).catch((error) => {
widgetPromise = undefined;
throw error;
});
return widgetPromise;
}
import { loadWidget } from "/assets/quoting24-loader.js";
const widget = await loadWidget();
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
Pour le mode intégré, ajoutez &mode=embedded&target=chat-assistance à l'URL et montez d'abord le conteneur. Ne dupliquez pas ce chargement avec une balise script statique.
loadWidget() signale que l'API est disponible. init() renvoie void : il met le profil en file si l'iframe n'est pas prêt et n'attend pas la session. Pour le résultat, utilisez isReady() et hasSessionError().
3. Champs de init()
Toutes les valeurs sont des chaînes. Omettez celles qui sont inconnues. N'envoyez pas null, des ID numériques ni un e-mail vide. Si un champ dépasse les limites ou si l'e-mail est mal formé, la requête est refusée.
| Champ | Limite et comportement |
|---|---|
external_id |
Maximum 100 caractères. Référence d'entreprise comparée exactement à web_code, dans l'organisation du widget. Les espaces ne sont pas retirés |
company_name |
Maximum 250 caractères après suppression des espaces en début et en fin. Crée une entreprise manquante seulement avec external_id. Une chaîne vide ou faite seulement d'espaces n'active pas la création |
name |
Maximum 250 caractères. Nom du contact ; il peut le mettre à jour dans la session courante |
email |
Adresse valide. Lors de l'initialisation publique, elle n'est enregistrée que sur une entreprise nouvelle. Elle n'est pas écrite sur le contact ni sur une entreprise déjà présente |
phone |
Maximum 80 caractères. Numéro du contact ; préférez le format international, par exemple +393331234567. Un conflit avec un numéro existant ne fusionne ni les contacts ni les conversations |
external_user_id |
Facultatif dans le cas de base. ID stable de la personne, non vide, maximum 100 caractères après trim. Seul, il ne restaure pas l'historique |
restore_id |
Credential privé, maximum 200 caractères, obtenu du backend. Exige l'external_user_id correspondant. Utilisez la valeur renvoyée sans la modifier |
Exemple lorsque le site connaît le compte. Les noms de champs dans le logiciel de gestion peuvent différer : mappez la référence entreprise sur external_id, la raison sociale sur company_name, la personne sur name, l'e-mail du compte sur email.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
Les libellés Nom et Téléphone du formulaire s'envoient comme name et phone. L'e-mail est email. N'envoyez pas Nom, Téléphone ni Email.
Le premier appel ouvre la conversation avec le nom et le téléphone et ne cherche pas d'entreprise. Le second suffit avec un e-mail valide. Dans le profil complet ci-dessus, phone est le numéro du contact et email est écrit sur l'entreprise seulement si cet appel la crée.
Gardez name et company_name séparés. Ne concaténez pas la raison sociale au nom d'utilisateur pour obtenir l'association. N'envoyez pas un champ web_code : le nom public est external_id. N'utilisez pas l'UUID interne de Quoting24 ni companies.code à la place de la valeur dans web_code.
init(profile) remplace le profil mémorisé par le loader. Il ne fait pas de fusion : passez les champs que vous voulez conserver.
Un nouvel invité peut démarrer avec au moins un nom, un téléphone, un e-mail valide ou un external_user_id, ou avec une entreprise trouvée ou créée via external_id. init({}) et company_name seul ne démarrent pas de nouvelle session. Si vous envoyez seulement un external_id inconnu, sans company_name, le widget demande les données via le formulaire.
init({ restoreId: "..." }) ne transmet pas le credential. Le champ est restore_id.
4. Associer ou créer l'entreprise
Le flux se déclenche lorsque Quoting24 traite l'initialisation, même sans message :
- S'il reçoit un
external_idnon vide, il cherche une entreprise de la même organisation dont leweb_codeest égal à cette valeur - S'il la trouve, il lie le contact. Le nom et l'e-mail de l'entreprise existante restent inchangés
- S'il ne la trouve pas et qu'il reçoit aussi un
company_namenon vide, il crée l'entreprise avecname = company_name,web_code = external_idet l'e-mail éventuel, puis lie le contact - S'il ne peut ni trouver ni créer l'entreprise faute de données, il continue avec le profil disponible. S'il est insuffisant pour un nouvel invité, il demande le formulaire. Les erreurs de base ou la limite de requêtes font échouer l'initialisation
| Données envoyées | Situation dans Quoting24 | Résultat |
|---|---|---|
external_id: "52", company_name: "Ricambi Stella S.r.l." |
web_code = "52" existe |
Utilise l'entreprise existante ; n'en modifie pas le nom |
| Mêmes données, plus un e-mail valide | web_code = "52" n'existe pas |
Crée l'entreprise avec le code web 52, la raison sociale et l'e-mail, et associe le contact |
external_id: "52", name: "mario.rossi" |
Aucune correspondance | Démarre l'invité sans nouvelle association d'entreprise |
external_id: "52", sans autres données |
Une correspondance existe | Démarre l'invité associé, avec le nom par défaut Visitatore |
external_id: "52", sans autres données |
Aucune correspondance | Demande les données du visiteur |
company_name sans external_id, plus name |
Quelconque | Démarre le visiteur. Ne cherche pas et ne crée pas d'entreprise |
La recherche n'utilise pas la raison sociale, l'e-mail, le téléphone, le numéro de TVA ni companies.code. "052" et "52" sont des valeurs différentes. Si vous importez d'abord le fichier clients, remplissez web_code avec la même valeur que le site enverra dans external_id.
L'association concerne le contact. Pour un contact déjà lié, un nouveau code valide peut changer son entreprise. Omettre le code, ou en envoyer un qui ne se résout pas, n'efface pas l'association précédente. Au changement de personne ou de compte, exécutez reset() : ne vous contentez pas de changer le profil.
Limites
web_code n'a pas de contrainte d'unicité. Avec plusieurs correspondances, la première est sélectionnée, par date de création croissante. Évitez les doublons : des requêtes simultanées qui ne trouvent pas encore l'entreprise peuvent en créer plusieurs. Le flux ne garantit pas une création atomique et ne remplace pas un import ou une synchronisation depuis le logiciel de gestion.
La création implicite est limitée à 20 tentatives par fenêtre de 60 secondes et par adresse IP, dans le processus backend qui traite la requête. Au-delà, l'initialisation échoue avec TOO_MANY_REQUESTS. Réessayez avec une attente. Évitez les boucles continues de init().
Les données viennent du navigateur et le visiteur peut les modifier. L'association ne prouve pas l'appartenance ni l'identité et n'accorde pas l'historique de l'entreprise. Envoyez company_name si vous voulez autoriser cette création publique. Pour mettre à jour des entreprises existantes, utilisez Quoting24 ou une intégration de gestion autorisée.
5. Connexion, déconnexion et changement de compte
Gardez une seule instance dans la structure principale d'une SPA. Un changement de page ne demande ni un nouveau script ni un reset.
Au changement de personne ou de compte, et à la déconnexion, appelez reset() avant le nouveau profil. Au rechargement avec la même connexion encore active, utilisez init() sans reset, comme au démarrage : vous conservez ainsi la session invité enregistrée. Le site doit distinguer le rechargement du changement effectif.
Appel aux changements d'authentification. profile est le profil du nouvel utilisateur, ou null à la déconnexion.
import { loadWidget } from "/assets/quoting24-loader.js";
let identityRevision = 0;
export async function applyWidgetIdentity(profile) {
const revision = ++identityRevision;
const snapshot = profile ? { ...profile } : null;
window.Quoting24Widget?.reset();
if (!snapshot) return;
const widget = await loadWidget();
if (revision !== identityRevision) return;
widget.reset();
widget.init(snapshot);
}
Gérez l'erreur de chargement dans l'interface du site, avec un nouvel essai. Si vous récupérez le profil depuis le backend avant cette fonction, invalidez aussi ces requêtes au changement d'utilisateur. Pendant l'attente, effacez tout de suite le profil précédent avec applyWidgetIdentity(null).
Le reset oublie la session invité de cette instance, le credential enregistré, le profil et le compteur de non-lus. Ce n'est pas une révocation serveur et cela ne termine pas les sessions sur les autres appareils. Le changement d'authentification doit aussi être appliqué dans les autres onglets ouverts.
Avec restauration de l'historique
Lorsque le profil inclut restore_id, téléchargez-le depuis le point d'accès authentifié du site après le reset(), et écartez la réponse si l'utilisateur a changé entre-temps. Le rechargement avec le même utilisateur réutilise le credential déjà enregistré et ne doit pas appeler reset().
import { loadWidget } from "/assets/quoting24-loader.js";
let authRevision = 0;
let pendingRecovery;
// Seulement à la connexion, à la déconnexion et au changement d'utilisateur.
// Ne pas l'appeler au rechargement avec le même utilisateur : reset() abandonnerait la session déjà en cours.
export async function applyWidgetAuth(user) {
const revision = ++authRevision;
const expectedUserId = user ? String(user.id) : null;
pendingRecovery?.abort();
const controller = new AbortController();
pendingRecovery = controller;
window.Quoting24Widget?.reset();
try {
const widget = await loadWidget();
if (revision !== authRevision) return;
widget.reset();
if (expectedUserId === null) return;
const response = await fetch("/api/widget/session", {
method: "POST",
credentials: "same-origin",
cache: "no-store",
signal: controller.signal,
headers: { "Content-Type": "application/json" },
body: "{}",
});
if (!response.ok) throw new Error("Restauration du widget indisponible");
const profile = await response.json();
if (revision !== authRevision) return;
if (profile.external_user_id !== expectedUserId || !profile.restore_id) {
throw new Error("Réponse de restauration inattendue");
}
const payload = {
external_user_id: profile.external_user_id,
restore_id: profile.restore_id,
};
for (const key of ["external_id", "company_name", "name", "email", "phone"]) {
if (profile[key]) payload[key] = profile[key];
}
widget.init(payload);
} catch (error) {
if (controller.signal.aborted || revision !== authRevision) return;
console.warn("Conversation d'assistance : restauration échouée.", error);
}
}
La boucle ne copie que les champs présents et non vides. Un e-mail "" ou un ID numérique ne doivent pas être envoyés.
API JavaScript
| Méthode | Usage |
|---|---|
init(profile) |
Fournit ou remplace le profil courant. Le loader ne fusionne pas les champs avec un profil précédent |
open() / close() / toggle() |
Ouvre, ferme ou bascule le widget flottant. Fermer n'est pas une déconnexion. En mode intégré, ils n'ont pas d'effet |
isReady() |
Initialisation de la session visiteur réussie. Reste faux pendant le formulaire de contact |
hasSessionError() |
Erreur de session signalée par l'iframe |
reset() |
Efface le profil, la session locale, le credential invité enregistré et le compteur de non-lus |
dispose() |
Démonte l'interface et les écouteurs. N'efface pas le credential invité enregistré ni l'objet API global |
Vous pouvez relier un bouton du site à window.Quoting24Widget?.open() lorsque le bouton flottant est apparu. N'attendez pas isReady() pour laisser un nouvel invité ouvrir le formulaire. Il n'existe pas d'événement public de disponibilité. Ces indicateurs ne décrivent pas tout l'état du réseau.
loadWidget() résolu ne signifie pas que le branding et la session sont prêts.
Démontage complet
Si vous devez retirer le widget, exécutez dispose(). Si le démontage coïncide avec la déconnexion, exécutez d'abord reset(). Avant de recharger le script, supprimez le global et remettez à zéro la Promise du loader.
export function removeWidget() {
window.Quoting24Widget?.reset();
window.Quoting24Widget?.dispose();
delete window.Quoting24Widget;
document.getElementById("quoting24-widget-script")?.remove();
widgetPromise = undefined;
}
widgetPromise est celle définie dans le module du loader. Exécutez le démontage après la fin du chargement. Ne le faites pas en concurrence avec un nouvel appel à loadWidget(). Pour les changements d'authentification, utilisez applyWidgetIdentity() ou applyWidgetAuth(), qui attendent le loader.
Le reset local ne révoque pas restore_id sur le serveur.
6. Restauration authentifiée
La session invité lit sa conversation la plus récente même après la fermeture. Les conversations visibles de la même personne sur un autre navigateur exigent ensemble external_user_id et restore_id.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "Mario Rossi",
phone: "+393331234567",
email: "mario.rossi@ricambi-stella.example",
external_user_id: "1042",
restore_id: session.restore_id,
});
La restauration ne fusionne pas d'anciennes sessions invité et ne partage pas l'historique entre collègues de la même entreprise.
Point d'accès sur le backend du site
Exemple à implémenter dans le portail, pas une URL de Quoting24.
{
"external_user_id": "1042",
"external_id": "52",
"company_name": "Ricambi Stella S.r.l.",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"restore_id": "<credential-renvoye-par-quoting24>"
}
Définissez Cache-Control: no-store. Identité et entreprise viennent seulement de la session serveur. Appliquez l'auth et le CSRF de l'application. Ne journalisez pas les corps qui contiennent le credential.
Si le portail expose déjà un point d'accès authentifié qui renvoie le restore_id de l'utilisateur courant pour ce widget, utilisez cette réponse et combinez-la avec l'ID de la personne et la référence d'entreprise.
Provisioning serveur à serveur
Chemin typique : /fastify/trpc/widget.provisionRestoreId
(ou /trpc/widget.provisionRestoreId si le backend est exposé directement).
POST /fastify/trpc/widget.provisionRestoreId
Authorization: Bearer <SECRET_INTEGRATION>
Content-Type: application/json
{
"widget_api_key": "<uuid-public-du-widget>",
"external_user_id": "1042",
"external_id": "52",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"attempt_id": "1042"
}
Les valeurs générées commencent par q24-restore-v1:. Ne les inventez pas et ne les dérivez pas d'ID publics.
| Entrée | Exigence |
|---|---|
widget_api_key |
UUID obligatoire, autorisé pour le secret |
external_user_id |
ID de personne stable, égal à celui de init() |
attempt_id |
Entier positif strictement croissant, en chaîne décimale, maximum 9223372036854775807 |
saved_restore_id |
Credential déjà enregistré. Omettez-le au premier provisioning |
external_id, name, email, phone |
Facultatifs, avec les mêmes limites que init() |
Schéma ensure, récupère ou crée :
- Authentifiez l'utilisateur et prenez un verrou sur la ligne de restauration, dans une transaction
- Allouez un nouvel
attempt_iddepuis une séquence après le verrou (CACHE 1, non cyclique ; la séquence survit au rollback) - Appelez Quoting24 avec
attempt_idet, s'il est présent,saved_restore_id - Enregistrez
result.data.restore_idet faites le commit - Seulement après le commit, renvoyez le credential au navigateur
Un saved_restore_id valide doit être réutilisé. L'omettre lorsqu'une clé valide existe déjà remplace la clé et invalide les sessions restaurées. Ne générez pas une nouvelle clé à chaque page view.
Si le provisioning distant réussit et que l'enregistrement local échoue, renvoyez une erreur. Ne renvoyez pas la clé non enregistrée. Timeouts limités.
company_name s'envoie dans init(). Le corps de provisioning ci-dessus ne l'inclut pas : l'association et la création de l'entreprise, décrites dans la section 4, ont lieu lorsque le navigateur initialise la session. L'e-mail de l'initialisation publique n'est écrit que sur une entreprise nouvelle.
Résultat côté widget
Quoting24 vérifie le credential et charge l'historique widget visible de cet utilisateur. Les conversations déjà fermées peuvent apparaître comme conversation précédente : les consulter ne rouvre pas l'ancienne conversation ; l'envoi reste sur la conversation courante.
Un credential non valide, d'un autre utilisateur ou révoqué est refusé, sans conversion automatique en session invité. Le bouton « Riprova » du widget ne demande pas tout seul un nouveau restore_id. L'interface actuelle est en italien.
7. Page, réseau et apparence
- Une seule instance par page
- CSP. Autorisez l'origine du widget dans
script-srcetframe-src, et l'origine API indiquée pour l'installation dansconnect-src(chargement des réglages graphiques). Autorisez les images configurées dansimg-src. Le loader insère des styles en ligne et n'expose pas d'option publique de nonce : vérifiez la CSP du site. Le CSS de la page ne modifie pas l'intérieur de l'iframe cross-origin - Connexions de l'iframe. Elle doit atteindre l'API, Realtime/WebSocket et le stockage de sa propre installation. Pour les filtres réseau, utilisez les hôtes effectifs de l'environnement
- Apparence et langue. Logo, couleur, position et messages se configurent dans Quoting24.
company_nameest la raison sociale du client. Il n'existe pas de paramètre publiclanguageoulocale; l'interface actuelle est en italien - Mobile. La conversation flottante peut occuper le viewport. En mode intégré, le site contrôle la hauteur et la largeur
- Pièces jointes et horaires. PDF, DOC/DOCX, JPG/JPEG et PNG. Jusqu'à 10 MiB par fichier et 100 MiB de pièces référencées par conversation. Hors horaires, le widget affiche la bannière et laisse la saisie disponible
8. Vérifier l'intégration
Utilisez un compte et un canal de test. Contrôlez le fichier clients dans Quoting24, en plus de la conversation.
| Essai | Résultat attendu |
|---|---|
| Navigateur neuf, script seul | Formulaire de contact, puis conversation |
Payload complet avec external_id déjà présent comme web_code |
Contact associé à l'entreprise existante ; nom et e-mail de l'entreprise inchangés |
| Payload complet avec un nouveau code | Entreprise créée avec code web, raison sociale et e-mail ; contact associé |
Nouveau code et nom, sans company_name |
Conversation démarrée, aucune entreprise créée |
| Nouveau code seul | Formulaire de contact |
| Raison sociale et nom seulement | Aucune entreprise cherchée ou créée par nom |
| E-mail vide, ID numérique ou raison sociale trop longue | Payload refusé |
Second init() dans la même session |
Poursuit la session invité. L'e-mail et le téléphone ne récupèrent pas la conversation d'une autre personne |
| Rechargement, navigation SPA, mobile | Une seule instance ; continuité invité si le stockage est là ; mise en page utilisable |
| Déconnexion ou changement de compte pendant le chargement | Session précédente effacée ; les résultats tardifs ne restaurent pas l'ancien profil |
Même utilisateur authentifié, autre navigateur, restore_id valide |
Historique de cette personne |
| Deux utilisateurs de la même entreprise | Credentials et historiques distincts |
| Invité, puis restauration | reset() au milieu ; les sessions ne se fusionnent pas |
| Credential non valide ou révoqué | Erreur, sans repli automatique vers l'invité |
| Conversations précédentes | Consultation seulement ; l'envoi reste sur la courante |
| Domaine non autorisé | Accès refusé |
| Réseau interrompu | Reconnexion ; brouillon conservé si l'envoi échoue |
Résolution des problèmes
| Symptôme | Contrôle |
|---|---|
| Le bouton n'apparaît pas | URL, clé, canal activé, domaines, CSP, requête widget.getBranding. Le seul global JavaScript ne prouve pas que le bouton existe. open() n'a pas d'effet tant que le branding n'est pas chargé, et il n'a pas d'effet en mode intégré |
Quoting24Widget n'est pas défini |
Attendez le chargement. Contrôlez le paramètre key |
| La conversation intégrée est invisible | Conteneur avant le loader, ID correct, hauteur supérieure à zéro |
Domain not allowed |
Nom d'hôte du site, sans schéma, port ni chemin. Vérifiez aussi le recettage. La page doit être servie en HTTP |
| Formulaire téléphone malgré le profil | Ordre asynchrone, snake_case, valeurs vides, reset() précédent, profil insuffisant pour un nouvel invité |
| L'entreprise n'est pas trouvée | Comparez external_id avec web_code dans la même organisation, espaces et zéros initiaux compris |
| L'entreprise manquante n'est pas créée | external_id et company_name doivent être tous deux non vides. Contrôlez la validation et la limite de création |
| Le nom ou l'e-mail de l'entreprise ne changent pas | Prévu : init() crée les entreprises manquantes et ne met pas à jour celles qui existent |
| L'e-mail n'apparaît pas sur le contact | Prévu pour l'initialisation publique : l'e-mail ne sert qu'à la création de l'entreprise |
TOO_MANY_REQUESTS |
Attendez et réessayez. Évitez les appels répétés ou les imports massifs via le widget |
| L'historique manque sur un autre appareil | Le payload entreprise ne le restaure pas. external_user_id et restore_id sont requis |
| Sessions restaurées invalidées | Les appels ensure doivent renvoyer saved_restore_id. N'émettez pas une nouvelle clé à chaque page view |
| « Riprova » ne suffit pas | Nouveau restore_id depuis votre point d'accès, puis reset() et init() |
| Après un changement de compte, l'ancienne conversation reste | reset() tout de suite au changement d'identité. Écartez les réponses asynchrones précédentes |
Après dispose() le script ne recrée pas le widget |
Supprimez le global et le script, remettez la Promise du loader à zéro, puis rechargez |
| L'interface reste en italien | Aucune option publique language ou locale sur le loader |
Références d'implémentation
- Loader et API JavaScript :
@frontend/src/public/widget/script.ts - Initialisation, association d'entreprise et création implicite :
@backend/fastify/routes/widget/initialize.ts - Tests d'initialisation et d'entreprises :
@backend/fastify/__tests__/widget/widgetInitialize.test.ts - Domaine et canal :
@backend/fastify/routes/widget/procedures/publicWidgetProcedure.ts - Provisioning de
restore_id:@backend/fastify/routes/widget/provisionRestoreId.ts