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

# Widget de chat web

> Incorpore o chat da MINDO no seu site: snippet, identidade de usuários logados (JWT) e personalização.

## O que é

O widget de chat web é **mais um canal** da MINDO (assim como WhatsApp, Instagram ou
ManyChat): um balão de chat que você incorpora no seu site com um `<script>` e que
conecta seus visitantes à sua caixa de entrada da MINDO. As mensagens que escreverem
chegam na mesma Caixa de entrada onde você já atende o WhatsApp, e sua equipe (ou seu
agente de IA) responde de lá — a resposta chega ao visitante **em tempo real**, sem que
ele precise recarregar a página.

Ele suporta dois tipos de visitante:

* **Anônimo** — não exige nada do seu backend. O widget gera um identificador de
  visitante próprio (guardado no `localStorage` do navegador) para reconhecê-lo entre
  as mensagens.
* **Identificado** — se o visitante já está logado no seu site, seu backend pode assinar
  a identidade dele para que a MINDO o reconheça. Isso une, sob um mesmo contato, todos
  os chats que esse usuário abrir em dispositivos diferentes, e deixa a conversa
  disponível quando ele voltar.

## Antes de começar

Tenha à mão o seguinte:

* Acesso a **Configurações → Canais** na MINDO com perfil de administrador.
* Acesso ao código do seu site (para colar um `<script>`).
* Se for identificar usuários logados: acesso ao backend do seu site, para assinar a
  identidade. A assinatura é calculada **sempre no servidor**, nunca no navegador.

## Passo a passo

<Steps>
  <Step title="Criar o canal Web">
    Na MINDO, vá em **Configurações** → **Canais** e procure a seção **Widget de chat
    web**. Clique em **+ Adicionar canal Web**.

    <Frame caption="Seção Widget de chat web em Configurações → Canais">
      <img src="https://mintcdn.com/mindo/9-0t-xPyhXm8rvsY/images/widget-web-canales.png?fit=max&auto=format&n=9-0t-xPyhXm8rvsY&q=85&s=4946fae76e7e1e249e3244108c534d8a" alt="Seção de canais Web na MINDO" width="1440" height="900" data-path="images/widget-web-canales.png" />
    </Frame>

    Preencha:

    * **Nome da sessão** — identifica o canal dentro da MINDO e, além disso, é o
      **título que o visitante vê** no cabeçalho do chat (por exemplo, "Chat da loja").
    * **Cor primária** — a cor do cabeçalho do painel, dos balões do visitante e do
      botão Enviar.
    * **Mensagem de boas-vindas** — a primeira mensagem que o visitante vê enquanto não
      houver conversa. Não é salva como mensagem nem aciona o agente.
    * **Agente** (opcional) — se você quiser que um agente de IA responda automaticamente
      neste canal.

    <Frame caption="Formulário de criação de um canal Web">
      <img src="https://mintcdn.com/mindo/4ooKWGhGXU37KYb5/images/widget-web-crear-canal.png?fit=max&auto=format&n=4ooKWGhGXU37KYb5&q=85&s=6cd3b1a7f9805edce02d2153ce603534" alt="Formulário para criar um canal Web" width="1440" height="900" data-path="images/widget-web-crear-canal.png" />
    </Frame>

    <Note>
      O agente **não responde no widget só por existir** na empresa: ele precisa estar
      atribuído aqui. Com **Sem agente**, as mensagens aguardam resposta humana na Caixa
      de entrada, como em qualquer outro canal.
    </Note>

    <Note>
      Uma empresa pode ter **vários canais Web** ao mesmo tempo —um por marca, por site
      ou por país—. Cada um tem seu token, sua cor e seu agente, e as conversas de um
      nunca se misturam com as do outro.
    </Note>
  </Step>

  <Step title="Copiar o snippet">
    Ao criar o canal, a MINDO mostra o snippet pronto para copiar:

    ```html theme={null}
    <script
      src="https://app.mindosoftware.com/widget.js"
      data-mindo-token="SEU_TOKEN_DE_CANAL"
      async
    ></script>
    ```

    Você também vai ver um **Secret HMAC** — só precisa dele se for identificar
    visitantes logados (Passo 4). Guarde-o em um lugar seguro; ele nunca deve ficar
    exposto no HTML do seu site.

    <Frame caption="Snippet e Secret HMAC do canal recém-criado">
      <img src="https://mintcdn.com/mindo/4ooKWGhGXU37KYb5/images/widget-web-snippet.png?fit=max&auto=format&n=4ooKWGhGXU37KYb5&q=85&s=dc04ba1af9f184db63bcd31046c364e2" alt="Snippet de incorporação e Secret HMAC do canal" width="1440" height="900" data-path="images/widget-web-snippet.png" />
    </Frame>

    O **token** do snippet é público: ele viaja no HTML do seu site e é esperado que
    fique visível. O **Secret** não: é exibido mascarado e só os usuários com perfil
    **ADMIN** da empresa podem vê-lo (para um MEMBER ou VIEWER a API devolve o campo
    vazio).
  </Step>

  <Step title="Colar o snippet no seu site">
    Cole o `<script>` antes do fechamento de `</body>` nas páginas onde você quer que o
    chat apareça. Não é preciso build step nem dependência — é um script vanilla,
    autocontido.

    Ao carregar a página você verá um balão de chat no canto inferior direito. Ao clicar,
    abre um painel com o chat (é um `<iframe>` para a MINDO, então seu site não precisa
    implementar nenhuma interface de chat).

    <Frame caption="Painel do widget aberto sobre um site de exemplo">
      <img src="https://mintcdn.com/mindo/4ooKWGhGXU37KYb5/images/widget-web-panel-abierto.png?fit=max&auto=format&n=4ooKWGhGXU37KYb5&q=85&s=52c8f093933c4b65361946cfcd9b1e80" alt="Painel do widget aberto" width="1280" height="720" data-path="images/widget-web-panel-abierto.png" />
    </Frame>

    <Note>
      Se o balão não aparecer, verifique o console do navegador: o erro mais comum é um
      `data-mindo-token` vazio ou incorreto.
    </Note>

    <Note>
      **O balão flutuante é sempre azul.** A *cor primária* que você configurou se aplica
      dentro do painel (cabeçalho, balões do visitante, botão Enviar), não ao balão. É o
      comportamento esperado, não um erro de configuração.
    </Note>

    A partir daqui, cada mensagem do visitante cria um chat na sua Caixa de entrada com
    canal **Web**, e sua equipe atende com o mesmo compositor de sempre. O chat do widget
    traz um dado que nenhum outro canal tem: o indicador **"Está na página"**, que informa
    se o visitante ainda está com o seu site aberto neste momento.

    <Frame caption="Chat do widget na Caixa de entrada, com o indicador de presença">
      <img src="https://mintcdn.com/mindo/4ooKWGhGXU37KYb5/images/widget-web-inbox-presencia.png?fit=max&auto=format&n=4ooKWGhGXU37KYb5&q=85&s=45e66289d464c192e4c518a3a263fa57" alt="Chat Web na Caixa de entrada da MINDO com indicador de presença" width="1440" height="900" data-path="images/widget-web-inbox-presencia.png" />
    </Frame>
  </Step>

  <Step title="(Opcional) Identificar visitantes logados">
    Se o seu site tem usuários logados e você quer que a MINDO os reconheça (em vez de
    tratá-los como anônimos), seu **backend** assina um **JWT** e o passa no atributo
    `data-mindo-identity`.

    O JWT é assinado com o algoritmo **HS256** usando o **Secret HMAC** do canal, e seu
    payload aceita estes campos:

    | Campo   | Obrigatório | O que é                                                                                     |
    | ------- | ----------- | ------------------------------------------------------------------------------------------- |
    | `sub`   | Sim         | O identificador único do seu usuário — o que você usa internamente.                         |
    | `exp`   | Sim         | Vencimento (timestamp Unix, em segundos).                                                   |
    | `name`  | Não         | Nome do usuário, para que o contato não fique como "Visitante".                             |
    | `email` | Não         | E-mail do usuário.                                                                          |
    | `phone` | Não         | Telefone em formato internacional. É o que permite unificar este contato com o do WhatsApp. |

    <Warning>
      **O `exp` não pode estar a mais de 7 dias.** Um JWT com vencimento mais distante é
      considerado inválido e o visitante fica anônimo. É um limite deliberado: se
      roubarem o token de alguém, sete dias é o máximo que esse roubo serve. Um TTL de 24
      a 72 horas é o habitual.
    </Warning>

    <CodeGroup>
      ```javascript Node.js theme={null}
      const jwt = require("jsonwebtoken");

      function assinarIdentidade(usuario, secretDoCanal, ttlSegundos = 60 * 60 * 24) {
        return jwt.sign(
          {
            sub: String(usuario.id),
            name: usuario.nome,
            email: usuario.email,
            phone: usuario.telefone,
            exp: Math.floor(Date.now() / 1000) + ttlSegundos,
          },
          secretDoCanal,
          { algorithm: "HS256" }
        );
      }
      ```

      ```python Python theme={null}
      import time
      import jwt  # pip install pyjwt

      def assinar_identidade(usuario, secret_do_canal, ttl_segundos=60 * 60 * 24):
          payload = {
              "sub": str(usuario.id),
              "name": usuario.nome,
              "email": usuario.email,
              "phone": usuario.telefone,
              "exp": int(time.time()) + ttl_segundos,
          }
          return jwt.encode(payload, secret_do_canal, algorithm="HS256")
      ```
    </CodeGroup>

    Renderize o token no `<script>` a partir de um template do lado do servidor:

    ```html theme={null}
    <script
      src="https://app.mindosoftware.com/widget.js"
      data-mindo-token="SEU_TOKEN_DE_CANAL"
      data-mindo-identity="{{ identityToken }}"
      async
    ></script>
    ```

    <Warning>
      O **Secret HMAC** é um segredo de servidor. Nunca o inclua em HTML, JavaScript do
      cliente, nem em um repositório público — ele só deve existir no seu backend, do
      mesmo modo que você guardaria qualquer outra API key.
    </Warning>

    Com a identidade assinada, o chat deixa de mostrar "Visitante ####" e passa a ser a
    pessoa real, com telefone e e-mail já preenchidos na ficha do contato:

    <Frame caption="O mesmo chat, já identificado como o contato real">
      <img src="https://mintcdn.com/mindo/9-0t-xPyhXm8rvsY/images/widget-web-contacto-identificado.png?fit=max&auto=format&n=9-0t-xPyhXm8rvsY&q=85&s=310b5dde9c7b6436926d3802022dd4b7" alt="Chat do widget com o contato identificado na Caixa de entrada" width="1440" height="900" data-path="images/widget-web-contacto-identificado.png" />
    </Frame>

    <Note>
      A assinatura é **opcional por design**: se faltar, estiver vencida ou for inválida,
      o visitante simplesmente fica anônimo — o chat nunca é bloqueado por um erro de
      assinatura. Isso permite testar sem risco de deixar o widget fora do ar.

      A contrapartida é que um JWT mal montado **falha em silêncio**. Se o contato
      continuar aparecendo como "Visitante ####", verifique nesta ordem: que o secret
      seja o do **mesmo canal** que o token, que o `exp` esteja em **segundos** (não
      milissegundos) e a menos de 7 dias, que a claim se chame `sub`, e que o algoritmo
      seja **HS256**.
    </Note>

    <AccordionGroup>
      <Accordion title="Método anterior: os três atributos com assinatura HMAC">
        Antes do JWT, a identidade era passada com três atributos separados. **Continua
        funcionando e não é preciso migrar**: se você usar os dois esquemas ao mesmo
        tempo, o JWT ganha, e se o JWT estiver quebrado ele cai para este.

        | Atributo               | O que é                                                     |
        | ---------------------- | ----------------------------------------------------------- |
        | `data-mindo-user`      | O identificador único do seu usuário (`userId`).            |
        | `data-mindo-expires`   | Timestamp Unix (segundos) até quando a assinatura é válida. |
        | `data-mindo-signature` | `HMAC_SHA256(secret, "{userId}:{expires}")` em hexadecimal. |

        <CodeGroup>
          ```javascript Node.js theme={null}
          const crypto = require("crypto");

          function assinarVisitante(userId, secretDoCanal, ttlSegundos = 60 * 60 * 24) {
            const expires = Math.floor(Date.now() / 1000) + ttlSegundos;
            const signature = crypto
              .createHmac("sha256", secretDoCanal)
              .update(`${userId}:${expires}`)
              .digest("hex");
            return { userId, expires, signature };
          }
          ```

          ```python Python theme={null}
          import hmac
          import hashlib
          import time

          def assinar_visitante(user_id, secret_do_canal, ttl_segundos=60 * 60 * 24):
              expires = int(time.time()) + ttl_segundos
              mensagem = f"{user_id}:{expires}".encode("utf-8")
              signature = hmac.new(
                  secret_do_canal.encode("utf-8"), mensagem, hashlib.sha256
              ).hexdigest()
              return {"userId": user_id, "expires": expires, "signature": signature}
          ```
        </CodeGroup>

        ```html theme={null}
        <script
          src="https://app.mindosoftware.com/widget.js"
          data-mindo-token="SEU_TOKEN_DE_CANAL"
          data-mindo-user="{{ userId }}"
          data-mindo-signature="{{ signature }}"
          data-mindo-expires="{{ expires }}"
          async
        ></script>
        ```

        **Por que o JWT é melhor se você está começando**: este esquema assina a string
        `userId:expires` usando `:` como separador, então não permite acrescentar o nome
        ou o e-mail sem ficar ambíguo. O JWT assina os dados exatos e permite enviar
        `name`, `email` e `phone` — que é o que faz o contato ter nome real e se unificar
        com o chat de WhatsApp dele.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="(Opcional) Identificar o visitante depois que ele fez login">
    Se o seu site é uma SPA, ou o visitante abre o chat **antes** de fazer login, não é
    preciso recarregar a página para que a MINDO o reconheça. O widget expõe um objeto
    `window.Mindo`:

    ```javascript theme={null}
    // Depois que seu usuário faz login, com o JWT que seu backend assinou:
    window.Mindo.setUser(identityToken);

    // Ao sair:
    window.Mindo.clearUser();
    ```

    O script carrega com `async`, então pode ser que `window.Mindo` ainda não exista
    quando o seu código rodar. Para isso existe uma fila de comandos: enfileire as
    chamadas e o widget as processa ao terminar de carregar.

    ```javascript theme={null}
    window.Mindo = window.Mindo || { q: [] };
    (window.Mindo.q = window.Mindo.q || []).push(["setUser", identityToken]);
    ```

    Você também pode fazer isso **sem escrever código de integração**: se a sua página
    guardar o JWT no `localStorage` sob a chave `mindo_identity`, o widget o pega
    sozinho. Serve para o caso do login em uma aba nova ou com um redirect de OAuth,
    porque o widget escuta as mudanças dessa chave. O mesmo valor pode ser difundido por
    um `BroadcastChannel` chamado `mindo-identity`.

    <Note>
      Além de `setUser` e `clearUser`, `window.Mindo` expõe `open()`, `close()` e
      `toggle()` para controlar o painel a partir da sua própria interface (por exemplo,
      um botão "Precisa de ajuda?"), e `on(evento, callback)` / `off(evento, callback)`
      para escutar `open`, `close`, `unread`, `identity`, `identity:cleared`,
      `auth:login` e `auth:register`.
    </Note>
  </Step>

  <Step title="(Opcional) Convidar a fazer login pelo chat">
    O widget pode mostrar ao visitante **anônimo** um bloco com botões de **Entrar** e
    **Criar conta** que apontam para as páginas do seu site. Ele desaparece sozinho
    quando o visitante fica identificado.

    <Frame caption="Botões de login e cadastro dentro do painel do widget">
      <img src="https://mintcdn.com/mindo/9-0t-xPyhXm8rvsY/images/widget-web-botones-auth.png?fit=max&auto=format&n=9-0t-xPyhXm8rvsY&q=85&s=5cb3c8b8f602b6ce6863936fdde8af86" alt="Widget com botões de entrar e criar conta" width="1280" height="720" data-path="images/widget-web-botones-auth.png" />
    </Frame>

    Configura-se em **Configurações → Canais → Widget de chat web**, editando o canal:
    ative **Botões de login** e preencha as URLs, os textos dos botões e a mensagem que
    fica acima deles.

    <Frame caption="Seção Botões de login ao editar o canal">
      <img src="https://mintcdn.com/mindo/9-0t-xPyhXm8rvsY/images/widget-web-config-botones-auth.png?fit=max&auto=format&n=9-0t-xPyhXm8rvsY&q=85&s=39bdb87b81ef4263f4ba4e81c1383101" alt="Configuração dos botões de login e cadastro do widget" width="1440" height="900" data-path="images/widget-web-config-botones-auth.png" />
    </Frame>

    | Campo                         | O que é                                              |
    | ----------------------------- | ---------------------------------------------------- |
    | **URL de login**              | Para onde mandar o visitante que já tem conta.       |
    | **URL de cadastro**           | Para onde mandá-lo se ainda não tiver.               |
    | **Textos dos botões**         | Opcionais. Por padrão, "Entrar" e "Criar conta".     |
    | **Mensagem acima dos botões** | A frase que explica por que vale a pena fazer login. |

    Basta **uma** das duas URLs: o botão que não tiver URL não é exibido.

    Também dá para fazer por API, com um usuário ADMIN da empresa:

    ```bash theme={null}
    curl -X PATCH https://api.mindosoftware.com/web-widget/channels/<ID_DO_CANAL>/ \
      -H "Authorization: Bearer <TOKEN_DE_UM_ADMIN>" \
      -H "Content-Type: application/json" \
      -d '{
        "auth": {
          "enabled": true,
          "loginUrl": "https://seusite.com/login",
          "registerUrl": "https://seusite.com/cadastro",
          "loginLabel": "Entrar",
          "registerLabel": "Criar conta",
          "prompt": "Faça login para ver seus pedidos e retomar a conversa de qualquer dispositivo."
        }
      }'
    ```

    Ao clicar, o widget abre a URL em uma aba nova. Se você preferir controlar a navegação
    (por exemplo, abrir o seu próprio modal de login), intercepte: devolver `false` no
    handler cancela a abertura.

    ```javascript theme={null}
    window.Mindo.on("auth:login", ({ url }) => {
      abrirMeuModalDeLogin();
      return false;
    });
    ```

    <Warning>
      As URLs devem ser **`https://`**. Uma `http://` é rejeitada ao salvar, e se tiver
      sido carregada na mão pelo admin o botão simplesmente não é renderizado.
    </Warning>

    <Warning>
      Envie o bloco no campo **`auth`**, não dentro de `config`. Um `config` explícito
      **substitui** o salvo (exceto `prechat` e `auth`), então um
      `{"config": {"auth": {...}}}` apaga a sua cor e a mensagem de boas-vindas. O campo
      `auth` plano é salvo sozinho, sem mexer no resto.
    </Warning>
  </Step>
</Steps>

## O que acontece depois?

* Cada mensagem que um visitante escreve cria (ou continua) um chat na sua Caixa de
  entrada da MINDO, com `Canal: Web`.
* Se você identificou o visitante e essa mesma pessoa escreve de outro dispositivo com o
  mesmo `sub`, a MINDO unifica o histórico sob um único contato.
* Se o JWT inclui `phone` e esse telefone já existe como contato de WhatsApp, a MINDO os
  unifica: fica **um contato com suas duas conversas** (a do widget e a do WhatsApp), em
  vez de duas pessoas diferentes.
* Se você tem um agente de IA atribuído ao canal, ele pode responder automaticamente; se
  não, o chat aguarda resposta humana como qualquer outro canal.

## Perguntas frequentes

<AccordionGroup>
  <Accordion title="Preciso instalar algo no meu site além do script?">
    Não. O `<script>` é autocontido: cria seu próprio balão e seu próprio painel de chat
    (um iframe). Não exige CSS, dependências nem build step.
  </Accordion>

  <Accordion title="Posso ter o widget em várias páginas do meu site?">
    Sim, cole o mesmo snippet em todas as páginas onde quiser que ele apareça. O visitante
    é identificado por um ID guardado no navegador dele, então se navegar entre suas
    páginas mantém a mesma conversa.
  </Accordion>

  <Accordion title="O que acontece se o visitante recarregar a página no meio da conversa?">
    Não perde nada: o widget recupera a conversa automaticamente ao recarregar e a mostra
    assim que ele abre o painel, sem precisar escrever de novo.
  </Accordion>

  <Accordion title="O visitante percebe se respondermos com o chat fechado?">
    Enquanto ele estiver com a sua página aberta, sim: o balão mostra um contador com as
    mensagens que chegaram com o painel fechado, e ele se limpa quando o visitante abre.

    <Frame caption="Contador de mensagens não lidas sobre o balão">
      <img src="https://mintcdn.com/mindo/9-0t-xPyhXm8rvsY/images/widget-web-badge.png?fit=max&auto=format&n=9-0t-xPyhXm8rvsY&q=85&s=4d96e3accaecc5cce4e2698e2694d748" alt="Balão do widget com o contador de não lidas" width="1280" height="720" data-path="images/widget-web-badge.png" />
    </Frame>

    Se ele fechou a aba, hoje não há aviso: na próxima vez que voltar ao seu site vai
    encontrar a resposta ao abrir o chat, mas nenhuma notificação chega até lá. Por isso
    vale pedir telefone ou e-mail pelo JWT (`phone` / `email`): com esses dados você pode
    retomar a conversa por outro canal.
  </Accordion>

  <Accordion title="O visitante pode mandar fotos ou áudios?">
    Não. O compositor do widget é **somente texto**, com limite de 4.000 caracteres por
    mensagem. Do lado do operador é possível enviar arquivos, e o visitante os recebe no
    painel.
  </Accordion>

  <Accordion title="Há limites de uso nos endpoints públicos?">
    Sim, limites anti-abuso por IP, bem acima do uso de uma pessoa real: **30 por minuto**
    para enviar mensagens, **20 por minuto** para a contagem de não lidas e **10 por
    minuto** para abrir sessão com identidade assinada.
  </Accordion>

  <Accordion title="O que acontece se eu não configurar a identificação?">
    Nada quebra: todos os visitantes ficam anônimos, cada um com seu próprio chat. Você
    pode adicionar a identificação mais adiante sem mudar nada do que já instalou.
  </Accordion>

  <Accordion title="Preciso migrar do método de três atributos para o JWT?">
    Não. O esquema anterior continua suportado e não tem data de encerramento. Migre só se
    quiser enviar o nome, o e-mail ou o telefone do usuário — coisas que o formato antigo
    não permite.
  </Accordion>

  <Accordion title="Posso restringir em quais sites meu token pode ser usado?">
    Sim. O canal tem uma lista de **origens permitidas** (`allowed_origins`): com a lista
    vazia —o valor padrão— não há restrição; com origens cadastradas, o header `Origin` da
    página tem que coincidir **exatamente** com alguma delas ou a API responde `403`.

    Ainda não está exposta no formulário de Configurações: carrega-se por API
    (`PATCH /web-widget/channels/<id>/` com `allowed_origins`) ou pelo admin. Se você
    ativar, inclua **todos** os domínios e subdomínios de onde o seu site é servido: um
    que falte deixa o chat sem funcionar nessas páginas.
  </Accordion>

  <Accordion title="Posso pedir telefone ou e-mail ao visitante antes que ele escreva?">
    **Hoje não ative.** A configuração existe no backend (`config.prechat`, com níveis
    `off` / `optional` / `required` para `phone` e `email`) mas **o widget ainda não
    mostra esse formulário**, então nunca envia esses dados.

    <Warning>
      Com `phone` ou `email` em `required`, a primeira mensagem de um visitante novo
      recebe `422 {"error": "Faltan campos obligatorios"}` e **o visitante não consegue
      iniciar a conversa**. Deixe em `off` até a interface existir.
    </Warning>
  </Accordion>

  <Accordion title="Tem custo adicional?">
    O canal Web não tem custo de mensageria como o WhatsApp (não depende da Meta). Consulte
    seu plano da MINDO se aplica algum limite de uso.
  </Accordion>
</AccordionGroup>
