> ## Documentation Index
> Fetch the complete documentation index at: https://docs.switchbord.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Installare il widget per sito web

> Guida di installazione drop-in per sviluppatori — un tag script, ricette per framework, riferimento SDK, identità firmata dei visitatori, note CSP, checklist di verifica e un prompt copia-incolla per agenti di coding AI.

Questa guida è per lo sviluppatore che incorpora il widget di chat di Switchbord su un sito web. L'installazione è un singolo tag script asincrono; tutto il resto di questa pagina è approfondimento opzionale — ricette per SPA, l'SDK JavaScript, l'identità firmata per gli utenti autenticati e la risoluzione dei problemi.

## Prerequisiti

Dall'operatore dell'area di lavoro (o da **Impostazioni → Integrazioni → Canali → Widget per sito web** se sei tu):

* Lo **snippet di installazione** (o anche solo lo **slug** del widget e l'URL base dell'API).
* La conferma che l'origine del tuo sito sia nell'elenco delle **origini consentite** del widget — lo schema + host esatti, ad es. `https://www.example.com`. `www` e apex sono origini diverse; aggiungile entrambe se entrambe servono il sito.

La base API ospitata è `https://api.switchbord.ai`. Le istanze self-hosted sostituiscono il proprio dominio API ovunque appaia qui sotto.

## Lo snippet

Incolla questo prima del tag di chiusura `</body>` in ogni pagina che deve mostrare il widget:

```html theme={null}
<script async src="https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js"></script>
```

Questa è l'intera installazione. Lo script inietta una bolla launcher e un iframe isolato; aspetto, testi e comportamento AI sono tutti controllati dalle Impostazioni di Switchbord e vengono pubblicati senza modifiche allo snippet.

<Tip>
  Se vuoi chiamare l'SDK prima che lo script sia stato caricato (ad esempio `setVisitor` in codice inline), aggiungi prima lo stub della coda di comandi — le chiamate effettuate prima del caricamento vengono accodate e riprodotte:

  ```html theme={null}
  <script>
    window.SwitchbordWidget = window.SwitchbordWidget || { q: [] };
    window.SwitchbordWidget.q.push(["setVisitor", { name: "Ada" }]);
  </script>
  <script async src="https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js"></script>
  ```
</Tip>

## Ricette per framework

<AccordionGroup>
  <Accordion title="HTML semplice / qualsiasi sito renderizzato lato server">
    Incolla lo snippet prima di `</body>` nel tuo layout/template di base così che appaia su ogni pagina. Fatto.
  </Accordion>

  <Accordion title="Next.js (App Router)">
    Usa `next/script` nel layout radice così che il widget si carichi una volta per sessione di navigazione, solo lato client:

    ```tsx theme={null}
    // app/layout.tsx
    import Script from "next/script";

    export default function RootLayout({ children }) {
      return (
        <html lang="en">
          <body>
            {children}
            <Script
              src="https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js"
              strategy="lazyOnload"
            />
          </body>
        </html>
      );
    }
    ```

    Non renderizzare lo script all'interno di pagine che si rimontano alla navigazione — il loader è idempotente (non creerà un secondo iframe), ma caricarlo una sola volta nel layout è più pulito.
  </Accordion>

  <Accordion title="React (SPA Vite / CRA)">
    Caricalo una volta in un effect di primo livello:

    ```tsx theme={null}
    useEffect(() => {
      if (document.getElementById("switchbord-widget-script")) return;
      const s = document.createElement("script");
      s.id = "switchbord-widget-script";
      s.async = true;
      s.src = "https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js";
      document.body.appendChild(s);
    }, []);
    ```
  </Accordion>

  <Accordion title="Google Tag Manager">
    Crea un tag **Custom HTML** contenente lo snippet, attivalo su **All Pages** (o con un trigger basato sul percorso della pagina se lo vuoi solo su alcune pagine) e pubblica. Ricorda che GTM viene eseguito dall'origine del tuo sito, quindi il requisito dell'allowlist delle origini rimane invariato.
  </Accordion>

  <Accordion title="WordPress">
    Puoi incollare lo snippet nel `footer.php` del tuo tema prima di `</body>`, oppure usare un qualsiasi plugin "insert headers and footers" e posizionarlo nella sezione footer. Le piattaforme site-builder (Webflow, Squarespace, Shopify) hanno tutte un'impostazione equivalente di "codice personalizzato prima di `</body>`".
  </Accordion>
</AccordionGroup>

## Content-Security-Policy

Se il tuo sito applica una CSP, consenti l'origine dell'API di Switchbord in queste direttive:

```
script-src  https://api.switchbord.ai
frame-src   https://api.switchbord.ai
connect-src https://api.switchbord.ai
```

L'interfaccia del widget viene eseguita all'interno di un iframe servito dall'origine dell'API, quindi la tua pagina deve solo caricare lo script loader e incorporare il frame — non sono richieste autorizzazioni per script o stili inline dal tuo sito.

## SDK JavaScript

Il loader espone `window.SwitchbordWidget`:

| Metodo                                     | Descrizione                                                                                             |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `open()` / `close()` / `toggle()`          | Controlla il pannello di chat.                                                                          |
| `show()` / `hide()`                        | Mostra o nasconde completamente il launcher e il pannello (ad es. nascondilo nelle pagine di checkout). |
| `send(text)`                               | Invia un messaggio per conto del visitatore.                                                            |
| `setVisitor({ name, email, anonymousId })` | Allega indizi di profilo del visitatore **non verificati**. Non sovrascrive mai un'identità verificata. |
| `setAttributes({ ... })`                   | Allega attributi personalizzati non verificati al visitatore.                                           |
| `login(token, attributes?)`                | Verifica il visitatore con un token di identità firmato (vedi sotto).                                   |
| `logout()`                                 | Rimuove l'identità verificata per questa sessione.                                                      |
| `on(event, fn)`                            | Sottoscrive gli eventi. Restituisce l'API per il chaining.                                              |

Eventi per `on(event, fn)`:

| Evento           | Si attiva quando                                                             |
| ---------------- | ---------------------------------------------------------------------------- |
| `ready`          | Il frame del widget si è avviato.                                            |
| `open` / `close` | Lo stato del pannello cambia.                                                |
| `message`        | La trascrizione si aggiorna (il payload include i messaggi).                 |
| `unread`         | Il conteggio dei non letti cambia mentre il pannello è chiuso (`{ count }`). |
| `error`          | Si è verificato un errore a livello di widget (`{ message }`).               |
| `identity`       | Un'azione di identità è stata risolta.                                       |

```html theme={null}
<script>
  window.SwitchbordWidget = window.SwitchbordWidget || { q: [] };
  window.SwitchbordWidget.q.push(["on", "ready", () => console.log("chat ready")]);
  window.SwitchbordWidget.q.push(["on", "unread", ({ count }) => console.log(count, "unread")]);
</script>
```

## Identità firmata (Secure Mode)

Se il tuo sito ha utenti autenticati, genera un JWT HS256 a breve scadenza sul **tuo server** e passalo a `login()` così che Switchbord sappia in modo verificabile chi è il visitatore. I visitatori verificati riprendono la conversazione tra dispositivi, e gli operatori vedono i dettagli di contatto verificati invece di sessioni anonime.

Configurazione:

1. In Switchbord, genera il **segreto di firma dell'identità del widget** sotto **Impostazioni → Integrazioni → Segreti dei provider** (`widget-identity-secret`). Viene memorizzato nel Vault dell'area di lavoro; il browser non lo vede mai.
2. Imposta la modalità di identità del widget nelle impostazioni del widget (e opzionalmente **Richiedi identità** per bloccare la messaggistica fino al login).
3. Genera i token lato server con questi claim:

| Claim                                                      | Valore                                                                                                                                                            |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sub`                                                      | L'ID esterno stabile del tuo utente (obbligatorio)                                                                                                                |
| `workspace_id`                                             | L'ID della tua area di lavoro Switchbord (obbligatorio)                                                                                                           |
| `channel_id`                                               | L'ID del canale del widget — mostrato nelle impostazioni del widget (obbligatorio)                                                                                |
| `aud`                                                      | Lo slug del widget (o l'audience configurata)                                                                                                                     |
| `iat` / `exp`                                              | Emissione e scadenza. TTL massimo 900 secondi per impostazione predefinita — genera il token per ogni caricamento di pagina, non memorizzare token a lunga durata |
| `iss`                                                      | Issuer opzionale, deve corrispondere alle impostazioni del widget se configurato                                                                                  |
| `email`, `name`, `phone`, `locale`, `avatar_url`, `custom` | Campi di profilo verificati opzionali                                                                                                                             |

<CodeGroup>
  ```ts Node.js theme={null}
  import { createHmac } from "node:crypto";

  const b64url = (v: string | Buffer) => Buffer.from(v).toString("base64url");

  export function mintWidgetToken(user: { id: string; email?: string; name?: string }) {
    const now = Math.floor(Date.now() / 1000);
    const header = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
    const payload = b64url(JSON.stringify({
      sub: user.id,
      workspace_id: process.env.SWITCHBORD_WORKSPACE_ID,
      channel_id: process.env.SWITCHBORD_WIDGET_CHANNEL_ID,
      aud: process.env.SWITCHBORD_WIDGET_SLUG,
      iat: now,
      exp: now + 300,
      email: user.email,
      name: user.name,
    }));
    const signature = createHmac("sha256", process.env.SWITCHBORD_WIDGET_IDENTITY_SECRET!)
      .update(`${header}.${payload}`)
      .digest("base64url");
    return `${header}.${payload}.${signature}`;
  }
  ```

  ```php PHP theme={null}
  function mintWidgetToken(array $user): string {
    $b64 = fn($v) => rtrim(strtr(base64_encode($v), '+/', '-_'), '=');
    $now = time();
    $header = $b64(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
    $payload = $b64(json_encode([
      'sub' => $user['id'],
      'workspace_id' => getenv('SWITCHBORD_WORKSPACE_ID'),
      'channel_id' => getenv('SWITCHBORD_WIDGET_CHANNEL_ID'),
      'aud' => getenv('SWITCHBORD_WIDGET_SLUG'),
      'iat' => $now,
      'exp' => $now + 300,
      'email' => $user['email'] ?? null,
      'name' => $user['name'] ?? null,
    ]));
    $sig = $b64(hash_hmac('sha256', "$header.$payload",
      getenv('SWITCHBORD_WIDGET_IDENTITY_SECRET'), true));
    return "$header.$payload.$sig";
  }
  ```

  ```python Python theme={null}
  import hmac, hashlib, json, time, base64, os

  def _b64url(data: bytes) -> str:
      return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

  def mint_widget_token(user: dict) -> str:
      now = int(time.time())
      header = _b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
      payload = _b64url(json.dumps({
          "sub": user["id"],
          "workspace_id": os.environ["SWITCHBORD_WORKSPACE_ID"],
          "channel_id": os.environ["SWITCHBORD_WIDGET_CHANNEL_ID"],
          "aud": os.environ["SWITCHBORD_WIDGET_SLUG"],
          "iat": now,
          "exp": now + 300,
          "email": user.get("email"),
          "name": user.get("name"),
      }).encode())
      sig = _b64url(hmac.new(
          os.environ["SWITCHBORD_WIDGET_IDENTITY_SECRET"].encode(),
          f"{header}.{payload}".encode(), hashlib.sha256).digest())
      return f"{header}.{payload}.{sig}"
  ```
</CodeGroup>

Poi, nella pagina per gli utenti autenticati:

```html theme={null}
<script>
  window.SwitchbordWidget = window.SwitchbordWidget || { q: [] };
  window.SwitchbordWidget.q.push(["login", "TOKEN_FROM_YOUR_SERVER"]);
</script>
```

<Warning>
  Il segreto di firma non deve mai raggiungere il browser. Genera i token in un endpoint server o iniettali nella pagina lato server. Funziona anche qualsiasi libreria JWT standard (jsonwebtoken, firebase/php-jwt, PyJWT) — gli esempi scritti a mano qui sopra servono solo a evitare una dipendenza.
</Warning>

## Verifica dell'installazione

1. Apri il tuo sito — la bolla launcher appare in basso a destra (o in basso a sinistra a seconda del tema) entro un secondo o due.
2. `curl https://api.switchbord.ai/api/v1/widgets/YOUR_WIDGET_SLUG/config` restituisce `200` con il JSON di configurazione del widget.
3. Invia un messaggio di prova dal widget — appare nella posta in arrivo di Switchbord in tempo reale.
4. Rispondi dalla posta in arrivo — il visitatore la vede (con il nome dell'operatore) entro pochi secondi; se il pannello è chiuso, appare un badge di non letto sul launcher.
5. Se la modalità AI è **bozza** o **invio automatico**, il messaggio di prova produce una bozza GBCA (o una risposta autonoma) dopo un breve debounce.

## Risoluzione dei problemi

| Sintomo                                                                                                     | Causa / soluzione                                                                                                                                                                                                                       |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Il launcher non appare mai, l'URL di config restituisce `404` con `origin_not_allowed` o `widget_not_found` | L'origine della tua pagina non è tra le origini consentite del widget, lo slug è sbagliato o il widget è disabilitato. Verifica schema + host esatti, incluso `www`.                                                                    |
| Risposte `429`                                                                                              | Rate limit dell'area di lavoro (120 richieste/minuto su tutto il traffico del widget) o protezione a livello di piattaforma. Riduci la frequenza; se il traffico legittimo supera questa soglia, parla con il tuo operatore Switchbord. |
| `identity_required` (`403`) durante l'invio                                                                 | Il widget ha **Richiedi identità** attivo — chiama `login()` con un token firmato prima di inviare messaggi.                                                                                                                            |
| `identity_secret_missing` (`503`) al login                                                                  | L'area di lavoro non ha ancora generato il segreto di firma dell'identità del widget.                                                                                                                                                   |
| `identity_signature_invalid` / `identity_*_mismatch`                                                        | Token firmato con il segreto sbagliato, oppure i claim `workspace_id` / `channel_id` / `aud` non corrispondono a questo widget.                                                                                                         |
| Il widget si carica ma i messaggi non raggiungono la posta in arrivo                                        | Controlla nella console del browser eventuali `connect-src` bloccati (CSP) o un ad-blocker che interferisce con l'origine dell'API.                                                                                                     |
| La conversazione si azzera per i visitatori anonimi                                                         | Previsto quando la memoria del browser viene cancellata. Usa l'identità firmata per thread persistenti.                                                                                                                                 |

## Prompt di installazione per agenti di coding AI

Consegna questo prompt a Cursor, Claude Code, Copilot o a qualsiasi agente di coding che lavora sul tuo sito web. Sostituisci i due segnaposto, incollalo e lascialo lavorare. Il prompt è volutamente in inglese, perché è pensato per essere incollato così com'è negli agenti di coding.

```text theme={null}
Install the Switchbord chat widget on this website.

CONTEXT
- Widget script URL: https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js
  (replace YOUR_WIDGET_SLUG; self-hosted Switchbord replaces the domain too)
- The site's public origin(s) must already be allowlisted in Switchbord
  (Settings -> Integrations -> Channels -> Website widget). Origins in use: YOUR_SITE_ORIGINS
- The widget is a single async script tag. It injects a launcher button and an
  isolated iframe. No npm package, no build step, no API key in the browser.

TASK
1. Detect the site's framework/stack by inspecting the repository.
2. Add the script tag so it loads once on every public-facing page, before </body>:
   <script async src="https://api.switchbord.ai/widgets/YOUR_WIDGET_SLUG/embed.js"></script>
   - Next.js App Router: use next/script with strategy="lazyOnload" in app/layout.tsx.
   - Next.js Pages Router: use next/script in pages/_app or _document (body, not head).
   - Plain HTML / server templates: add to the shared base layout/footer template.
   - React SPA (Vite/CRA): inject once from a top-level useEffect, guarded by an
     element-id check so hot reloads and remounts cannot add it twice.
   - Do NOT add it to admin-only or checkout-critical pages if the repo clearly
     separates those layouts; otherwise site-wide is correct.
3. If the site has a Content-Security-Policy (check meta tags, next.config,
   nginx/vercel headers config), extend it:
   script-src, frame-src, connect-src must each include https://api.switchbord.ai
4. Do not add any other code, wrappers, or configuration. Do not attempt to call
   widget APIs from the server. The optional JS SDK is window.SwitchbordWidget
   (open/close/setVisitor/login) with a pre-load command queue
   window.SwitchbordWidget.q — only wire it if the task explicitly asks.

VERIFY
1. Build/serve the site locally.
2. Confirm exactly one script tag for /widgets/YOUR_WIDGET_SLUG/embed.js is present
   in the rendered HTML of at least two different pages.
3. curl -s -o /dev/null -w "%{http_code}" https://api.switchbord.ai/api/v1/widgets/YOUR_WIDGET_SLUG/config
   should print 200 (404 means wrong slug or origin not yet allowlisted — report it, don't work around it).
4. Report: files changed, where the tag was placed, CSP changes made (if any),
   and the local verification results. Note that the launcher only renders on
   allowlisted origins, so localhost may not show it unless localhost is allowlisted.
```

<Note>
  Test in localhost: aggiungi `http://localhost:3000` (o la tua porta di sviluppo) alle origini consentite del widget durante lo sviluppo, e rimuovilo prima del go-live. Un'allowlist vuota consente tutte le origini — va bene per un primo smoke test, non per la produzione.
</Note>
