Widget de chat JavaScript
Intégrez le chat en direct de Deskwoot sur n'importe quel site web avec une seule balise <script>. Pas d'étape de build, et aucune connexion au tableau de bord n'est demandée aux visiteurs.
Sur cette page
Installation
Collez cet extrait de code dans le modèle de votre page, idéalement juste avant la balise fermante </body>. Il charge le widget de façon asynchrone, il ne bloque donc pas l'affichage de la page.
<script>
window.deskwootSettings = {
widgetToken: "YOUR_WIDGET_TOKEN"
};
(function (d, t) {
var g = d.createElement(t);
var s = d.getElementsByTagName(t)[0];
g.src = "https://deskwoot.com/widget/deskwoot-loader.js";
g.async = true;
g.defer = true;
s.parentNode.insertBefore(g, s);
})(document, "script");
</script>C'est tout. Le bouton du chat apparaît tout de suite sur votre page. Pas de paquet npm, pas de bundler, pas d'étape de build : il fonctionne avec Next.js, React, Vue, les thèmes Shopify, WordPress, le HTML simple et partout où JavaScript s'exécute.
Trouver le jeton de votre widget
- Connectez-vous au tableau de bord Deskwoot avec un compte administrateur. Vos visiteurs n'ont jamais besoin de se connecter.
- Allez dans Paramètres → Boîtes de réception.
- Créez une nouvelle boîte de réception de type Chat en direct, ou ouvrez celle que vous avez déjà.
- Dans l'onglet Widget, la carte Installation du widget affiche l'extrait de code avec votre propre
widgetToken. Copiez-le tel quel.
widgetToken = une boîte de réception. Les conversations de ce widget arrivent toujours dans cette boîte de réception, où vos agents les prennent en charge. Pour les diriger vers une autre équipe ou un autre groupe d'agents, créez une deuxième boîte de réception et utilisez son jeton sur une autre page.Configuration
Toutes les options se trouvent dans window.deskwootSettings. Seul widgetToken est obligatoire.
| Option | Type | Par défaut | Description |
|---|---|---|---|
| widgetToken | string | aucun | Obligatoire. Identifiant de la boîte de réception, disponible dans le tableau de bord. |
| user | object | aucun | Identité signée d'un visiteur connecté. Voir Vérification d'identité. |
| embedded | boolean | false | Remplit un conteneur ou tout l'écran au lieu de flotter dans un coin. Voir Mode intégré. |
| container | string | Element | aucun | Uniquement avec embedded : le sélecteur CSS ou l'élément à remplir. Sans cette option, le widget occupe toute la fenêtre d'affichage. |
| openAnchor | string | Element | { x, y } | aucun | Élément d'où s'ouvre le panneau sur ordinateur : un sélecteur CSS, un élément ou un point { x, y }. |
| openAlign | string | "auto" | Le coin du panneau placé sur l'ancre : auto, bottom-left, bottom-right, top-left ou top-right. |
window.deskwootSettings = {
widgetToken: "YOUR_WIDGET_TOKEN",
// Optional: a verified, logged in visitor (see Identity verification)
user: {
identifier: "user_4711",
identifierHash: "<HMAC-SHA256 of identifier, from your server>",
email: "jane@example.com",
name: "Jane Doe"
},
// Optional: open the panel out of your own button on desktop
openAnchor: "#support-button",
openAlign: "auto"
};Vérification d'identité
Quand les visiteurs sont connectés sur votre site, indiquez au widget qui ils sont. Leurs conversations arrivent alors avec leur nom, leur e-mail et leur historique, et non comme celles d'un visiteur anonyme.
Le widget n'accepte qu'une identité signée par votre serveur. Vous signez l'id de l'utilisateur avec un secret propre à la boîte de réception. Les visiteurs vérifiés discutent sous leur vrai nom et leur vrai e-mail, et passent le formulaire avant le chat ainsi que l'étape de l'e-mail. Le secret reste sur votre serveur : Deskwoot confirme le visiteur grâce à la signature, sans aucun accès à vos systèmes.
Vous trouverez votre secret de signature dans les paramètres de la boîte de réception de votre site web, dans l'onglet Formulaire avant le chat, sur la carte Vérification d'identité.
import crypto from "crypto";
const identifierHash = crypto
.createHmac("sha256", process.env.DESKWOOT_IDENTITY_SECRET)
.update(userId) // your stable user id, as a string
.digest("hex");window.deskwootSettings = {
widgetToken: "YOUR_WIDGET_TOKEN",
user: {
identifier: "user_4711", // your stable user id
identifierHash: "<HMAC-SHA256 of identifier, computed on your server>",
email: "jane@example.com", // optional
name: "Jane Doe", // optional
attributes: { plan: "Pro" }, // optional, shown on the contact
attributesIssuedAt: 1760781600, // optional: unix seconds you signed them at
attributesHash: "<HMAC-SHA256 of the signed attributes, from your server>" // optional
}
};identifier est l'identifiant stable de votre utilisateur et le seul champ signé. email, name et attributes (une liste simple de textes, comme un forfait ou un identifiant d'entreprise, affichée sur le contact) sont des informations facultatives. Un visiteur vérifié arrive toujours sur le même contact, avec ses conversations passées, ses notes et ses attributs sous les yeux. Le bot IA ne répond à ce visiteur dans le chat en direct qu'à partir des attributs signés (voir ci-dessous), et seulement s'il a le droit de consulter les contacts dans « Ce que Fynn peut faire ».
Si votre application connecte l'utilisateur après le chargement de la page, appelez window.deskwoot.identify(user) dès que vous avez l'utilisateur signé. Cela met à jour le visiteur actuel sans rechargement. Si l'utilisateur est déjà connecté au chargement de la page, définissez plutôt deskwootSettings.user avant le script du widget : le chat reste alors ouvert d'un rechargement à l'autre. Avec identify() après le chargement, chaque rechargement commence par un chat vide, qui revient dès que l'utilisateur est vérifié.
Attributs signés
identifierHash ne signe que l'id de l'utilisateur, un visiteur peut donc modifier attributes dans le navigateur. Pour que le bot IA réponde à partir de ces attributs, signez-les sur votre serveur et envoyez attributesHash et attributesIssuedAt avec eux. Les attributs non signés, et ceux écrits via l'API ou par votre équipe, restent visibles sur le contact, mais le bot ne les lit jamais. Le bot peut dire au visiteur ce que contient un attribut signé, alors ne signez que ce que le visiteur a le droit de voir.
attributesHash est le HMAC-SHA256 en hexadécimal minuscule, avec le même secret de boîte de réception, de quatre lignes reliées par un saut de ligne (\n) : le texte deskwoot-attributes-v1, l'id de l'utilisateur, attributesIssuedAt et le JSON canonique des attributs. JSON canonique : clés triées par point de code Unicode, sans espaces, caractères hors ASCII écrits tels quels (non échappés), et uniquement des chaînes, des nombres, true, false et null comme valeurs (pas d'objets ni de listes imbriqués). C'est exactement ce qu'écrit json.dumps(attributes, sort_keys=True, separators=(",", ":"), ensure_ascii=False) en Python.
Envoyez les nombres décimaux et les très grands entiers (au-delà de 9007199254740991) sous forme de chaînes. Python écrit 1.0 là où JavaScript écrit 1, ces nombres ne donneraient donc pas le même hash des deux côtés.
attributesIssuedAt est l'heure unix, en secondes entières, à laquelle vous avez signé l'ensemble. Deskwoot accepte un ensemble vieux de 24 heures au plus et en avance de 5 minutes au plus sur son horloge, et jamais un ensemble plus ancien que le dernier utilisé pour cet utilisateur : signez donc à nouveau à chaque chargement de page. En dehors de ces limites, le visiteur reste vérifié, mais les attributs de cette requête sont ignorés (rien ne change sur le contact) et la réponse de vérification indique attributesIgnored avec out_of_range ou stale. Un hash incorrect est refusé comme un identifierHash incorrect, et le visiteur discute alors de façon anonyme. Il en va de même pour un ensemble signé qui dépasse les limites : 30 clés au maximum, des clés de 64 caractères au plus, des valeurs de 1024 caractères au plus, et aucune clé que Deskwoot se réserve, comme source.
Chaque ensemble signé remplace ce que le bot peut lire pour cet utilisateur : une clé que vous omettez reste sur le contact, mais le bot ne la lit plus, et une clé que vous signez avec la valeur null est supprimée du contact.
import crypto from "crypto";
// Keys sorted by Unicode code point, no spaces, non ASCII characters raw.
// Values: strings, integers, true, false or null.
function canonicalJson(attributes) {
const keys = Object.keys(attributes).sort((a, b) => {
const x = Array.from(a), y = Array.from(b);
for (let i = 0; i < Math.min(x.length, y.length); i++) {
const d = x[i].codePointAt(0) - y[i].codePointAt(0);
if (d !== 0) return d;
}
return x.length - y.length;
});
return "{" + keys.map((k) => JSON.stringify(k) + ":" + JSON.stringify(attributes[k])).join(",") + "}";
}
const attributesIssuedAt = Math.floor(Date.now() / 1000);
const message = "deskwoot-attributes-v1\n" + userId + "\n" + attributesIssuedAt + "\n" + canonicalJson(attributes);
const attributesHash = crypto
.createHmac("sha256", process.env.DESKWOOT_IDENTITY_SECRET)
.update(message)
.digest("hex");import hashlib, hmac, json, os, time
attributes_issued_at = int(time.time())
canonical = json.dumps(attributes, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
message = "deskwoot-attributes-v1\n" + user_id + "\n" + str(attributes_issued_at) + "\n" + canonical
attributes_hash = hmac.new(
os.environ["DESKWOOT_IDENTITY_SECRET"].encode(),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()secret test_identity_secret
identifier user_4711
attributesIssuedAt 1760781600
attributes { "plan": "Pro", "status": "verified", "trades": 12, "vip": true,
"referrer": null, "city": "Zürich", "Zone": "EU" }
canonical JSON {"Zone":"EU","city":"Zürich","plan":"Pro","referrer":null,"status":"verified","trades":12,"vip":true}
signed message (4 lines, joined by \n, no line break at the end):
deskwoot-attributes-v1
user_4711
1760781600
{"Zone":"EU","city":"Zürich","plan":"Pro","referrer":null,"status":"verified","trades":12,"vip":true}
attributesHash bd363335977152a3b73cfb6c042c9afc823f18eab73faff36acddfffd0b1e1f5API JavaScript
Dès que le loader s'est exécuté, window.deskwoot (aussi disponible sous le nom window.deskwootWidget) vous permet de piloter le widget depuis votre propre interface, par exemple avec un lien Nous contacter dans votre navigation qui ouvre le chat au lieu d'une page de support.
| Méthode | Ce qu'elle fait |
|---|---|
| open(options?) | Ouvre le panneau du chat. Les options facultatives anchor, align et offset le font s'ouvrir depuis votre propre élément sur ordinateur. |
| close() | Ferme le panneau. |
| toggle(options?) | Ouvre le panneau, ou le ferme s'il est déjà ouvert. Accepte les mêmes options que open(). |
| identify(user) | Connecte un utilisateur vérifié après le chargement de la page. Voir Vérification d'identité. |
| window.__deskwootUnread | Nombre de messages d'agents non lus pour ce visiteur. Il se met à jour pendant que le panneau est fermé. |
Les appels faits pendant que le widget démarre encore attendent dans une file et s'exécutent dès qu'il est prêt. Inutile donc d'attendre ou de vérifier en boucle.
// window.deskwoot (alias window.deskwootWidget) exists as soon as the
// loader script has run. Calls made while the widget is still starting
// are queued, so there is no need to wait or poll. Only code that runs
// before the loader itself needs the guard below.
function openDeskwoot() {
if (window.deskwoot) {
window.deskwoot.open();
}
}
openDeskwoot(); // open the chat panel
// window.deskwoot.close(); // close it again
// window.deskwoot.toggle(); // open or close
// Unread agent messages for this visitor:
console.log(window.__deskwootUnread);Mode intégré
Définissez embedded: true quand le widget se trouve dans un conteneur qui doit occuper tout l'écran, en général une WebView d'application mobile, un écran Contact en pleine page ou une fenêtre modale qui a son propre cadre.
En mode intégré, le bouton du chat, la fenêtre pop-up du déclencheur et le bouton de fermeture sont masqués, et le panneau remplit position:fixed; inset:0 sans ombre, sans coins arrondis et sans le décalage de 20px. Faites pointer container vers un élément pour ne remplir que cet élément. L'application mobile Deskwoot utilise ce mode sur son écran Contacter le support.
<div id="support-pane" style="height: 600px"></div>
<script>
window.deskwootSettings = {
widgetToken: "YOUR_WIDGET_TOKEN",
embedded: true,
container: "#support-pane" // leave out to fill the whole viewport
};
</script>S'ouvre automatiquement et remplit son conteneur.
En mode intégré, le panneau s'ouvre au chargement et remplit son conteneur. Le bouton du chat et le bouton de fermeture sont masqués, c'est donc votre application ou votre page qui gère la navigation. L'API JavaScript reste disponible pour l'ouvrir, le fermer ou basculer son affichage :
<button onclick="window.deskwoot.open()">Contact Support</button>Emplacements recommandés
Trois emplacements qui fonctionnent bien pour le support dans une application :
Élément dans la barre latérale ou la navigation du bas (comme le fait Deskwoot)
Placez un élément Contacter le support dans votre navigation existante, à côté de Paramètres ou Aide. Il ressemble à n'importe quel autre lien, mais ouvre le chat au lieu d'une nouvelle page.
<a href="#" onclick="window.deskwoot.open(); return false;" class="nav-item">
<svg class="icon"><!-- chat bubble icon --></svg>
Contact Support
</a>Élément dans le menu d'aide ou un menu déroulant
Si vous avez déjà un menu Aide ou Compte, ajoutez une ligne Discuter avec le support en haut. Elle s'intègre bien à côté d'éléments comme Documentation, Raccourcis clavier ou Commentaires.
<div role="menuitem" onclick="window.deskwoot.open()">
Chat with support
<span class="kbd">?</span>
</div>Bouton flottant dans le coin
Un petit bouton flottant qui remplace le bouton du chat masqué. Pratique quand votre application n'a pas de barre de navigation toujours visible.
<button
id="support-fab"
onclick="window.deskwoot.open({ anchor: this })"
style="position:fixed;bottom:20px;right:20px;border:0;border-radius:9999px;padding:12px 18px;cursor:pointer;font-weight:600"
>
Help
</button>
<!--
open() and toggle() take an optional anchor, so the panel grows out of
your own element instead of the default corner:
open({ anchor: this }) // grow from this button
open({ anchor: "#support-fab" }) // or from a CSS selector
open({ anchor: { x: 120, y: 640 } }) // or from a viewport point
anchor CSS selector, DOM element or { x, y } (null = default corner)
align "auto" (default), "bottom-left", "bottom-right",
"top-left" or "top-right": the corner it grows from
offset { x, y } in px, to fine tune the position
window.deskwootSettings.openAnchor and .openAlign make this the
default for every open(); open({ anchor: null }) opens one time in
the default corner. Desktop only; phones always get full screen.
-->Conseils
- Appelez
deskwoot.toggle()au lieu deopen()si vous voulez que le même déclencheur ferme le panneau quand il est déjà ouvert. - Affichez un badge de messages non lus sur votre déclencheur avec
window.__deskwootUnread, un nombre qui se met à jour pendant que le panneau est fermé. - Placez le déclencheur au même endroit sur toutes les pages qui demandent une connexion, pour que les utilisateurs sachent toujours où trouver le support.
Pour l'installation standard sur un site web, vous n'avez pas besoin de votre propre déclencheur : deskwoot-loader.js affiche le bouton du chat et gère le clic.
Content Security Policy
Si votre site envoie un en-tête Content Security Policy, autorisez l'origine de Deskwoot. Une politique minimale typique :
script-src 'unsafe-inline' https://deskwoot.com;
connect-src https://deskwoot.com;
frame-src https://deskwoot.com;
style-src 'unsafe-inline';Le code d'installation est un script en ligne, donc script-src a besoin de 'unsafe-inline', d'un nonce ou d'un hash. style-src 'unsafe-inline' est nécessaire parce que le widget ajoute une petite feuille de style à votre page, et frame-src parce que le chat s'exécute dans des frames servis par deskwoot.com. Des questions sur votre politique ? Écrivez à hello@deskwoot.com.
Questions fréquentes
Voici les réponses courtes. Pour tout le reste, demandez à Fynn ci-dessous.
Mes visiteurs ont-ils besoin d'un compte Deskwoot pour discuter ?
Non. Seuls vous et votre équipe vous connectez au tableau de bord pour répondre. Les visiteurs écrivent simplement dans le widget.
Le widget fonctionne-t-il dans les SPA qui changent de route sans rechargement ?
Oui. Chargez le script une seule fois au démarrage de votre application (par exemple dans _app.tsx ou root.tsx), et il reste actif pendant la navigation. Rien ne doit être relancé quand la route change.