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

# Instale o Widget para Site

> Guia de instalação direta para desenvolvedores — uma tag de script, receitas por framework, referência do SDK, identidade assinada de visitante, notas de CSP, checklist de verificação e um prompt pronto para copiar para agentes de programação com IA.

Este guia é para o desenvolvedor que vai incorporar o widget de chat do Switchbord em um site. A instalação é uma única tag de script assíncrona; todo o resto desta página é aprofundamento opcional — receitas para SPAs, o SDK JavaScript, identidade assinada para usuários autenticados e resolução de problemas.

## Pré-requisitos

Do operador do workspace (ou de **Settings → Integrations → Channels → Website widget**, se for você):

* O **snippet de instalação** (ou apenas o **slug** do widget e a URL base da API).
* Confirmação de que a origem do seu site está na lista de **origens permitidas** do widget — o esquema + host exatos, p. ex. `https://www.example.com`. `www` e o domínio raiz são origens diferentes; adicione ambos se ambos servirem o site.

A base da API hospedada é `https://api.switchbord.ai`. Instâncias self-hosted substituem seu próprio domínio de API em todos os lugares em que ele aparece abaixo.

## O snippet

Cole isto antes da tag de fechamento `</body>` em cada página que deve exibir o widget:

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

Essa é a instalação inteira. O script injeta uma bolha de abertura e um iframe isolado; aparência, textos e comportamento de IA são todos controlados em Settings do Switchbord e publicados sem mudanças no snippet.

<Tip>
  Se você quiser chamar o SDK antes de o script ter carregado (por exemplo `setVisitor` em código inline), adicione primeiro o stub de fila de comandos — chamadas feitas antes do carregamento são enfileiradas e reexecutadas:

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

## Receitas por framework

<AccordionGroup>
  <Accordion title="HTML puro / qualquer site renderizado no servidor">
    Cole o snippet antes de `</body>` no seu layout/template base para que ele apareça em todas as páginas. Pronto.
  </Accordion>

  <Accordion title="Next.js (App Router)">
    Use `next/script` no layout raiz para que o widget carregue uma vez por sessão de navegação, apenas no cliente:

    ```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>
      );
    }
    ```

    Não renderize o script dentro de páginas que remontam a cada navegação — o loader é idempotente (não vai criar um segundo iframe), mas carregá-lo uma única vez no layout é mais limpo.
  </Accordion>

  <Accordion title="React (SPA com Vite / CRA)">
    Carregue uma vez em um effect de nível superior:

    ```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">
    Crie uma tag de **Custom HTML** contendo o snippet, dispare-a em **All Pages** (ou com um acionador por caminho de página se você só quiser em algumas páginas) e publique. Lembre-se de que o GTM dispara a partir da origem do seu site, então o requisito da lista de origens permitidas permanece o mesmo.
  </Accordion>

  <Accordion title="WordPress">
    Cole o snippet no `footer.php` do seu tema antes de `</body>`, ou use qualquer plugin do tipo "insert headers and footers" e coloque-o na seção de rodapé. Plataformas de construção de sites (Webflow, Squarespace, Shopify) todas têm uma configuração equivalente de "código personalizado antes de `</body>`".
  </Accordion>
</AccordionGroup>

## Content-Security-Policy

Se o seu site usa uma CSP, permita a origem da API do Switchbord nestas diretivas:

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

A interface do widget roda dentro de um iframe servido a partir da origem da API, então sua página só precisa carregar o script do loader e incorporar o frame — nenhuma permissão de script inline ou estilo é exigida do seu site.

## SDK JavaScript

O loader expõe `window.SwitchbordWidget`:

| Método                                     | Descrição                                                                                             |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `open()` / `close()` / `toggle()`          | Controla o painel de chat.                                                                            |
| `show()` / `hide()`                        | Mostra ou oculta o botão de abertura e o painel por completo (p. ex. ocultar em páginas de checkout). |
| `send(text)`                               | Envia uma mensagem em nome do visitante.                                                              |
| `setVisitor({ name, email, anonymousId })` | Anexa dicas **não verificadas** de perfil do visitante. Nunca sobrescreve identidade verificada.      |
| `setAttributes({ ... })`                   | Anexa atributos personalizados não verificados ao visitante.                                          |
| `login(token, attributes?)`                | Verifica o visitante com um token de identidade assinado (veja abaixo).                               |
| `logout()`                                 | Descarta a identidade verificada desta sessão.                                                        |
| `on(event, fn)`                            | Assina eventos. Retorna a API para encadeamento.                                                      |

Eventos para `on(event, fn)`:

| Evento           | Dispara quando                                                             |
| ---------------- | -------------------------------------------------------------------------- |
| `ready`          | O frame do widget inicializou.                                             |
| `open` / `close` | O estado do painel muda.                                                   |
| `message`        | A transcrição é atualizada (o payload inclui as mensagens).                |
| `unread`         | A contagem de não lidas muda enquanto o painel está fechado (`{ count }`). |
| `error`          | Ocorreu um erro no nível do widget (`{ message }`).                        |
| `identity`       | Uma ação de identidade foi resolvida.                                      |

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

## Identidade assinada (Secure Mode)

Se o seu site tem usuários autenticados, emita um JWT HS256 de curta duração no **seu servidor** e passe-o para `login()` para que o Switchbord saiba de forma verificável quem é o visitante. Visitantes verificados retomam a conversa entre dispositivos, e os operadores veem dados de contato verificados em vez de sessões anônimas.

Configuração:

1. No Switchbord, gere o **segredo de assinatura de identidade do widget** em **Settings → Integrations → Provider secrets** (`widget-identity-secret`). Ele é armazenado no Vault do workspace; o navegador nunca o vê.
2. Defina o modo de identidade do widget nas configurações do widget (e, opcionalmente, **Require identity** para bloquear mensagens até o login).
3. Emita tokens no lado do servidor com estas claims:

| Claim                                                      | Valor                                                                                                                                    |
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `sub`                                                      | O ID externo estável do seu usuário (obrigatório)                                                                                        |
| `workspace_id`                                             | O ID do seu workspace no Switchbord (obrigatório)                                                                                        |
| `channel_id`                                               | O ID de canal do widget — exibido nas configurações do widget (obrigatório)                                                              |
| `aud`                                                      | O slug do widget (ou a audiência configurada)                                                                                            |
| `iat` / `exp`                                              | Emissão e expiração. TTL máximo de 900 segundos por padrão — emita por carregamento de página, não faça cache de tokens de longa duração |
| `iss`                                                      | Emissor opcional; precisa corresponder às configurações do widget se configurado                                                         |
| `email`, `name`, `phone`, `locale`, `avatar_url`, `custom` | Campos opcionais de perfil verificado                                                                                                    |

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

Depois, na página para usuários autenticados:

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

<Warning>
  O segredo de assinatura nunca deve chegar ao navegador. Emita tokens em um endpoint de servidor ou injete-os na página no lado do servidor. Qualquer biblioteca JWT padrão (jsonwebtoken, firebase/php-jwt, PyJWT) também funciona — os exemplos manuais acima existem apenas para evitar uma dependência.
</Warning>

## Verifique a instalação

1. Abra o seu site — a bolha de abertura aparece no canto inferior direito (ou inferior esquerdo, conforme o tema) em um ou dois segundos.
2. `curl https://api.switchbord.ai/api/v1/widgets/YOUR_WIDGET_SLUG/config` retorna `200` com o JSON de configuração do widget.
3. Envie uma mensagem de teste pelo widget — ela aparece na caixa de entrada do Switchbord em tempo real.
4. Responda pela caixa de entrada — o visitante a vê (com o nome do operador) em poucos segundos; se o painel estiver fechado, um selo de não lidas aparece no botão de abertura.
5. Se o modo de IA for **draft** ou **auto-send**, a mensagem de teste produz um rascunho do GBCA (ou uma resposta autônoma) após um breve debounce.

## Resolução de problemas

| Sintoma                                                                                                         | Causa / correção                                                                                                                                                                                                |
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| O botão de abertura nunca aparece, a URL de config retorna `404` com `origin_not_allowed` ou `widget_not_found` | A origem da sua página não está nas origens permitidas do widget, o slug está errado, ou o widget está desabilitado. Verifique o esquema + host exatos, incluindo `www`.                                        |
| Respostas `429`                                                                                                 | Limite de taxa do workspace (120 requisições/minuto em todo o tráfego do widget) ou proteção no nível da plataforma. Reduza o ritmo; se o tráfego legítimo exceder isso, fale com o seu operador do Switchbord. |
| `identity_required` (`403`) ao enviar                                                                           | O widget está com **Require identity** ativado — chame `login()` com um token assinado antes de enviar mensagens.                                                                                               |
| `identity_secret_missing` (`503`) no login                                                                      | O workspace ainda não gerou o segredo de assinatura de identidade do widget.                                                                                                                                    |
| `identity_signature_invalid` / `identity_*_mismatch`                                                            | Token assinado com o segredo errado, ou as claims `workspace_id` / `channel_id` / `aud` não correspondem a este widget.                                                                                         |
| O widget carrega mas as mensagens não chegam à caixa de entrada                                                 | Verifique no console do navegador se há bloqueio de `connect-src` (CSP) ou um bloqueador de anúncios interferindo na origem da API.                                                                             |
| A conversa reinicia para visitantes anônimos                                                                    | Comportamento esperado quando o armazenamento do navegador é limpo. Use identidade assinada para conversas duráveis.                                                                                            |

## Prompt de instalação para agentes de programação com IA

Entregue isto ao Cursor, Claude Code, Copilot ou qualquer agente de programação que trabalhe no seu site. Substitua os dois placeholders, cole e deixe-o executar. O prompt está intencionalmente em inglês, pois foi feito para ser colado literalmente em agentes de programação que operam em inglês.

```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>
  Testes em localhost: adicione `http://localhost:3000` (ou a sua porta de desenvolvimento) às origens permitidas do widget durante o desenvolvimento, e remova antes de ir ao ar. Uma lista de permissões vazia libera todas as origens — aceitável para um primeiro teste rápido, não para produção.
</Note>
