JavaScript-Chat-Widget

Bette Deskwoot-Live-Chat auf jeder Website mit einem einzigen <script>-Tag ein. Kein Build-Step, kein Dashboard-Login für Besucher erforderlich.

Auf dieser Seite

Installation

Füge dieses Snippet in dein Seiten-Template ein, idealerweise direkt vor dem schließenden </body>-Tag. Es lädt das Widget asynchron, sodass es das Rendering nicht blockiert.

<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>

Das war's. Die Chat-Bubble erscheint sofort auf deiner Seite. Kein npm-Paket, kein Bundler, kein Build-Schritt: Es läuft in Next.js, React, Vue, Shopify-Themes, WordPress, reinem HTML und überall sonst, wo JavaScript läuft.

Deinen Widget-Token finden

  1. Melde dich mit einem Admin-Konto im Deskwoot-Dashboard an. Deine Besucher brauchen nie ein Login.
  2. Gehe zu Einstellungen → Posteingänge.
  3. Erstelle einen neuen Posteingang vom Typ Live-Chat oder öffne deinen bestehenden.
  4. Im Tab Widget zeigt die Karte Widget installieren das Snippet mit deinem eigenen widgetToken. Kopiere es unverändert.
Ein widgetToken = ein Posteingang. Konversationen aus diesem Widget landen immer in diesem Posteingang, wo deine Agenten sie aufnehmen. Um zu einem anderen Team oder einer anderen Agentengruppe zu routen, erstelle einen zweiten Posteingang und verwende seinen Token auf einer anderen Seite.

Konfiguration

Alle Optionen stehen in window.deskwootSettings. Nur widgetToken ist Pflicht.

OptionTypStandardBeschreibung
widgetTokenstringkeinerErforderlich. Posteingangs-Identifikator aus dem Dashboard.
userobjectkeinerSignierte Identität eines eingeloggten Besuchers. Siehe Nutzer-Verifizierung.
embeddedbooleanfalseFüllt einen Container oder den ganzen Bildschirm, statt in einer Ecke zu schweben. Siehe Embedded-Modus.
containerstring | ElementkeinerNur mit embedded: der CSS-Selektor oder das Element, das gefüllt wird. Ohne die Option füllt das Widget den ganzen Viewport.
openAnchorstring | Element | { x, y }keinerElement, aus dem das Panel auf dem Desktop herauswächst: ein CSS-Selektor, ein Element oder ein Punkt { x, y }.
openAlignstring"auto"Welche Panel-Ecke am Anker sitzt: auto, bottom-left, bottom-right, top-left oder top-right.
Farbe, Position des Launchers, helles oder dunkles Theme und die Begrüßungstexte setzt du nicht im Code. Du änderst sie pro Posteingang unter Einstellungen → Posteingänge → dein Posteingang → Widget → Aussehen des Widgets, und das Widget lädt sie bei jedem Start.
Vollständiges Beispiel
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"
};

Nutzer-Verifizierung

Wenn Besucher auf deiner Website eingeloggt sind, sag dem Widget, wer sie sind. Ihre Konversationen kommen dann mit Name, E-Mail und Verlauf an statt als anonymer Besucher.

Das Widget akzeptiert nur eine Identität, die dein Server signiert hat. Du signierst die Nutzer-ID mit einem Geheimnis, das zum Posteingang gehört. Verifizierte Besucher chatten unter ihrem echten Namen und ihrer E-Mail und überspringen das Vor-Chat- und das E-Mail-Formular. Das Geheimnis bleibt auf deinem Server, Deskwoot bestätigt den Besucher also anhand der Signatur, ganz ohne Zugriff auf deine Systeme.

Dein Signatur-Geheimnis findest du in den Einstellungen deines Website-Posteingangs, im Tab Vor-Chat-Formular, in der Karte Nutzer-Verifizierung.

Berechne den Hash auf deinem Server (Node.js)
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");
Verifizierter Besucher
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 ist deine stabile Nutzer-ID und das einzige signierte Feld. email, name und attributes (eine flache Liste von Texten wie Tarif oder Firmen-ID, die am Kontakt angezeigt wird) sind optional. Ein verifizierter Besucher landet jedes Mal beim selben Kontakt, mit früheren Konversationen, Notizen und Attributen im Blick. Der KI-Bot antwortet diesem Besucher im Live-Chat nur anhand signierter Attribute (siehe unten), und nur, wenn sein Datenzugriff Kontakte umfasst.

Für Single-Page-Apps, die den Nutzer erst nach dem Laden der Seite einloggen, rufe window.deskwoot.identify(user) auf, sobald du den signierten Nutzer hast. Das aktualisiert den aktuellen Besucher ohne Neuladen. Ist der Nutzer beim Laden der Seite schon eingeloggt, setze stattdessen deskwootSettings.user vor dem Widget-Skript: Dann bleibt der Chat über jedes Neuladen hinweg offen. Mit identify() nach dem Laden startet jedes Neuladen mit einem leeren Chat, der zurückkommt, sobald der Nutzer verifiziert ist.

Der Hash muss von deinem Server kommen. Berechne ihn nie im Browser und liefere das Geheimnis nie an den Client aus. Wer das Geheimnis hat, kann sich als deine Nutzer ausgeben.

Signierte Attribute

identifierHash signiert nur die Nutzer-ID, ein Besucher kann attributes also im Browser ändern. Damit der KI-Bot daraus antworten darf, signiere sie auf deinem Server und sende attributesHash und attributesIssuedAt mit. Unsignierte Attribute und Attribute, die per API oder von deinem Team geschrieben wurden, erscheinen weiter am Kontakt, aber der Bot liest sie nie. Der Bot kann dem Besucher sagen, was in einem signierten Attribut steht, signiere also nur, was der Besucher sehen darf.

attributesHash ist der HMAC-SHA256 als Hex in Kleinbuchstaben, mit demselben Inbox-Secret, über vier Zeilen, verbunden durch einen Zeilenumbruch (\n): den Text deskwoot-attributes-v1, die Nutzer-ID, attributesIssuedAt und das kanonische JSON der Attribute. Kanonisches JSON: Schlüssel nach Unicode-Codepoint sortiert, keine Leerzeichen, Zeichen außerhalb von ASCII unverändert (nicht escaped) und als Werte nur Texte, Zahlen, true, false und null (keine verschachtelten Objekte oder Listen). Genau das schreibt Pythons json.dumps(attributes, sort_keys=True, separators=(",", ":"), ensure_ascii=False).

Sende Dezimalzahlen und sehr große Ganzzahlen (über 9007199254740991) als Text. Python schreibt 1.0, wo JavaScript 1 schreibt, solche Zahlen ergäben auf beiden Seiten also nicht denselben Hash.

attributesIssuedAt ist die Unix-Zeit in ganzen Sekunden, zu der du die Attribute signiert hast. Deskwoot verwendet sie, wenn sie höchstens 24 Stunden alt und höchstens 5 Minuten vor seiner Uhr ist, und nie, wenn sie älter ist als die zuletzt verwendete für diesen Nutzer. Signiere also bei jedem Seitenaufruf neu. Außerhalb davon ist der Besucher trotzdem verifiziert, aber die Attribute dieser Anfrage werden ignoriert (am Kontakt ändert sich nichts), und die Antwort der Verifizierung enthält attributesIgnored mit out_of_range oder stale. Ein falscher Hash wird abgelehnt wie ein falscher identifierHash, der Besucher chattet dann anonym. Ebenso signierte Attribute über den Grenzen: höchstens 30 Schlüssel, Schlüssel bis 64 Zeichen, Werte bis 1024 Zeichen und kein Schlüssel, den Deskwoot für sich reserviert, etwa source.

Jede signierte Menge ersetzt, was der Bot für diesen Nutzer lesen darf: Ein Schlüssel, den du weglässt, bleibt am Kontakt, aber der Bot liest ihn nicht mehr, und ein Schlüssel, den du als null signierst, wird vom Kontakt entfernt.

Attribute auf deinem Server signieren (Node.js)
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");
Dasselbe in Python
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()
Testvektor: dein Code muss genau dieses kanonische JSON und diesen Hash ergeben
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      bd363335977152a3b73cfb6c042c9afc823f18eab73faff36acddfffd0b1e1f5

JavaScript-API

Sobald der Loader gelaufen ist, steuerst du das Widget mit window.deskwoot (auch erreichbar als window.deskwootWidget) aus deiner eigenen Oberfläche, zum Beispiel mit einem Kontakt-Link in deiner Navigation, der den Chat öffnet statt einer Support-Seite.

MethodeWas sie tut
open(options?)Öffnet das Chat-Panel. Mit den optionalen Werten anchor, align und offset wächst es auf dem Desktop aus deinem eigenen Element heraus.
close()Schließt das Panel.
toggle(options?)Öffnet das Panel oder schließt es, wenn es schon offen ist. Nimmt dieselben Optionen wie open().
identify(user)Meldet einen verifizierten Nutzer nach dem Laden der Seite an. Siehe Nutzer-Verifizierung.
window.__deskwootUnreadAnzahl ungelesener Agenten-Nachrichten für diesen Besucher. Sie aktualisiert sich, während das Panel geschlossen ist.

Aufrufe, während das Widget noch startet, warten in einer Warteschlange und laufen, sobald es bereit ist. Du musst also nicht warten oder abfragen.

Den Chat aus deinem eigenen Code öffnen
// 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);

Embedded-Modus

Setze embedded: true, wenn das Widget in einem Container lebt, der den ganzen Bildschirm einnimmt, typischerweise eine WebView in einer Mobile-App, eine ganzseitige Kontaktseite oder ein Modal mit eigenem Rahmen.

Im Embedded-Modus sind Launcher-Bubble, Trigger-Popup und Schließen-Button ausgeblendet, und das Panel füllt position:fixed; inset:0 ohne Schatten, ohne runde Ecken und ohne 20px Abstand. Zeig mit container auf ein Element, um nur dieses zu füllen. Die Deskwoot App auf dem Handy nutzt diesen Modus auf ihrem Support-Bildschirm.

<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>

Öffnet automatisch und füllt den Container.

Im Embedded-Modus öffnet sich das Panel beim Laden und füllt seinen Container. Launcher-Bubble und Schließen-Button sind ausgeblendet, deine App oder Seite übernimmt also die Navigation. Die JavaScript-API steht weiterhin bereit, um es zu öffnen, zu schließen oder umzuschalten:

Minimaler Trigger
<button onclick="window.deskwoot.open()">Contact Support</button>

Empfohlene Platzierungen

Drei Platzierungen, die sich für Support in einer App bewährt haben:

Eintrag in Seitenleiste oder unterer Navigation (so macht es Deskwoot)

Platziere einen Eintrag Kontakt-Support in deiner bestehenden Navigation, neben Einstellungen oder Hilfe. Er sieht aus wie jeder andere Link, öffnet aber den Chat statt einer neuen Seite.

<a href="#" onclick="window.deskwoot.open(); return false;" class="nav-item">
  <svg class="icon"><!-- chat bubble icon --></svg>
  Contact Support
</a>

Eintrag im Hilfe-Menü oder Dropdown

Wenn du schon ein Hilfe- oder Konto-Menü hast, füge oben eine Zeile Mit Support chatten hinzu. Sie passt gut zu Einträgen wie Doku, Tastenkürzel oder Feedback.

<div role="menuitem" onclick="window.deskwoot.open()">
  Chat with support
  <span class="kbd">?</span>
</div>

Schwebender Button in der Ecke

Ein kleiner schwebender Button, der die ausgeblendete Bubble ersetzt. Praktisch, wenn deine App keine dauerhaft sichtbare Navigationsleiste hat.

<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.
-->

Tipps

  • Rufe deskwoot.toggle() statt open() auf, wenn derselbe Trigger das Panel schließen soll, wenn es bereits offen ist.
  • Zeig mit window.__deskwootUnread ein Ungelesen-Badge an deinem Trigger, eine Zahl, die sich aktualisiert, während das Panel geschlossen ist.
  • Platziere den Trigger auf jeder Seite hinter deinem Login an derselben Stelle, damit Nutzer immer wissen, wo der Support ist.

Für die normale Website-Installation brauchst du keinen eigenen Trigger: deskwoot-loader.js zeichnet die Bubble und kümmert sich um den Klick.

Content Security Policy

Wenn deine Website einen Content-Security-Policy-Header sendet, erlaube den Ursprung von Deskwoot. Eine typische minimale Richtlinie:

script-src  'unsafe-inline' https://deskwoot.com;
connect-src https://deskwoot.com;
frame-src   https://deskwoot.com;
style-src   'unsafe-inline';

Das Installations-Snippet ist ein Inline-Script, script-src braucht also 'unsafe-inline', eine Nonce oder einen Hash. style-src 'unsafe-inline' ist nötig, weil das Widget ein kleines Stylesheet in deine Seite einfügt, und frame-src, weil der Chat in Frames von deskwoot.com läuft. Fragen zu deiner Richtlinie? Schreib an hello@deskwoot.com.

Häufig gestellte Fragen

Die kurzen Antworten stehen hier. Für alles andere frag unten Fynn.

Brauchen meine Besucher ein Deskwoot-Konto, um zu chatten?

Nein. Nur du und dein Team melden sich im Dashboard an, um zu antworten. Besucher tippen einfach ins Widget.

Funktioniert das Widget in SPAs, die Routen im Browser wechseln?

Ja. Lade das Script einmal beim Start deiner App (zum Beispiel in _app.tsx oder root.tsx), und es bleibt beim Navigieren erhalten. Bei einem Routenwechsel muss nichts neu laufen.

Interaktive Deskwoot-Demo