Pular para o conteúdo principal

Identificação de usuários no Webchat

9 min de leituraAtualizado em 26 out. 2026

Por padrão, o widget de webchat identifica o visitante por um cookie/localStorage anônimo no navegador — trocar de dispositivo ou limpar o navegador perde o histórico de conversa. Esta funcionalidade permite que a plataforma que hospeda o widget (o seu sistema — ex.: uma área de aluno, um portal de cliente) informe à Atalia quem é o usuário logado, para que:

  • o visitante veja o histórico de conversa em qualquer navegador/dispositivo, sempre que estiver logado na sua plataforma;
  • o backend da sua plataforma possa consultar as conversas e mensagens desse usuário via uma API de leitura.

É opt-in por canal: sem nenhuma configuração, o webchat continua funcionando exatamente como antes (visitante anônimo).

Escopo

A identificação vale só para o webchat, e só dentro do mesmo canal. Ela não cruza com WhatsApp nem com outros canais da mesma empresa.

Como funciona, em uma frase​

O seu backend assina um JWT curto (HS256) provando quem é o usuário logado, e entrega esse token ao widget — no carregamento da página ou dinamicamente. A Atalia verifica a assinatura com um segredo que só você e a Atalia conhecem, e a partir daí trata aquele visitante como o usuário identificado.

1. Gerar o segredo no painel​

  1. Acesse Configurações › Canais, abra o canal de webchat desejado e vá em Editar canal.
  2. Na aba Instalação, seção Identificação de usuários, clique em Gerar segredo. O segredo é criado na hora, sem precisar salvar.
  3. Copie o segredo exibido — ele só aparece por completo neste momento. Guarde-o com o mesmo cuidado que uma senha de banco de dados: qualquer pessoa com o segredo pode assinar um JWT válido para qualquer usuário desse canal.
  4. (Opcional) Configure um template de link, por exemplo https://minhaplataforma.com/alunos/{external_id}. A Atalia usa esse template para, na ficha do contato, oferecer um atalho para o perfil do usuário na sua plataforma. Clique em Salvar, no topo da página, para gravar o template.

Regenerar o segredo invalida o anterior imediatamente. JWTs assinados com o segredo antigo passam a ser rejeitados (INVALID_SIGNATURE). Sessões de visitantes já identificados continuam válidas até logout ou expiração natural — regenerar o segredo não desloga ninguém.

O segredo nunca deve ir para o navegador

O JWT é assinado no seu backend, nunca no navegador do usuário. Se o segredo vazar para o frontend (JavaScript do lado do cliente), qualquer visitante poderia se passar por qualquer outro usuário.

2. Gerar o JWT no seu backend​

Claims do JWT (HS256, assinado com o segredo do canal):

ClaimObrigatórioDescrição
subsimIdentificador único e estável do usuário na sua plataforma (external_id). 1–255 caracteres.
expsimExpiração. Vida útil máxima de 1 hora a partir da emissão — tokens com exp maior são rejeitados.
namenãoNome do usuário. Sobrescreve o nome do contato na Atalia quando presente.
emailnãoE-mail do usuário. Sobrescreve o e-mail do contato quando presente, e é usado para localizar um contato existente (ver Merge de contato abaixo).

Gere um JWT novo a cada identificação — não reutilize um token além do tempo de vida dele. Recomendamos 5 a 60 minutos de validade.

Node.js (jsonwebtoken)​

import jwt from 'jsonwebtoken';

const ATALIA_WEBCHAT_SECRET = process.env.ATALIA_WEBCHAT_SECRET; // segredo do canal, nunca no frontend

function buildAtaliaIdentity(user) {
return jwt.sign(
{
sub: String(user.id),
name: user.name,
email: user.email,
},
ATALIA_WEBCHAT_SECRET,
{ algorithm: 'HS256', expiresIn: '15m' },
);
}

PHP (firebase/php-jwt)​

<?php
use Firebase\JWT\JWT;

$ataliaWebchatSecret = getenv('ATALIA_WEBCHAT_SECRET'); // segredo do canal, nunca no frontend

function buildAtaliaIdentity(array $user, string $secret): string {
$now = time();
$payload = [
'sub' => (string) $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'iat' => $now,
'exp' => $now + 900, // 15 minutos
];
return JWT::encode($payload, $secret, 'HS256');
}
Relógio do servidor

O relógio que assina o token precisa estar sincronizado (NTP). Um servidor com relógio adiantado gera um JWT "do futuro" — a verificação de expiração não tem tolerância (leeway) e o identify falha.

3. Entregar o JWT ao widget​

No carregamento da página​

Se o usuário já está logado quando a página com o widget carrega, defina o token antes do script do widget:

<script>
window.AtaliaChatSettings = {
identity: '<jwt gerado no seu backend>',
};
</script>
<script src="https://.../webchat.js" async></script>

Dinamicamente (SPA, login/logout em tela)​

// fila global disponível mesmo antes do widget carregar
window.AtaliaChat = window.AtaliaChat || [];

// Depois do login do usuário na sua plataforma:
window.AtaliaChat.push(['identify', jwtDoBackend]);
// ou, com o widget já carregado:
AtaliaChat.identify(jwtDoBackend)
.then(() => console.log('Identificado'))
.catch((err) => console.warn('Falha ao identificar', err.code));

// No logout da sua plataforma:
AtaliaChat.logout();

// Assinar eventos:
AtaliaChat.on('identified', ({ externalId, contactId }) => {
// ...
});

AtaliaChat.identify(jwt) devolve uma Promise que rejeita com um objeto { code } quando o token é inválido ou expirado (ver Códigos de erro) — o widget nunca mostra um erro visível ao usuário final nesse caso; ele apenas segue anônimo.

Chamar identify de novo com o mesmo sub é idempotente (não gera uma sessão nova). Chamar com um sub diferente do identificado atualmente equivale a um logout seguido de identify.

4. Eventos disponíveis​

Assine via AtaliaChat.on(evento, callback).

EventoQuando disparaPayload
readyWidget carregado e pronto para uso—
identifiedidentify concluído com sucesso{ externalId, contactId }
logoutSessão identificada encerrada (chamada explícita ou expiração)—
conversation:startedUma nova conversa foi aberta pelo visitante{ conversationId }
conversation:closedA conversa atual foi encerrada{ conversationId }
message:receivedMensagem nova recebida no widget (sem o conteúdo — só o identificador){ conversationId, messageId }
unread:changedContador de não lidas mudou{ count }

5. Página hospedada​

A página hospedada do webchat (/chat/:token, ou o domínio customizado configurado no canal) aceita o fragmento de URL #identity=<jwt> (depois do #, não do ?). Ao carregar, a página consome o token da mesma forma que AtaliaChat.identify() e remove o fragmento da URL (history.replaceState) — o JWT não permanece visível na barra de endereço nem no histórico do navegador.

https://minhaplataforma.com/atendimento#identity=<jwt gerado no seu backend>

Por que fragmento e não ?identity= (querystring): o navegador nunca envia o fragmento ao servidor — ele é cortado antes da requisição HTTP sair. Um JWT em querystring chega ao nosso servidor (e a qualquer proxy/CDN no meio do caminho) e pode acabar em log de acesso; um JWT em fragmento nunca sai do navegador do visitante.

Códigos de erro​

CódigoOnde apareceSignificado
NO_SECRET_CONFIGUREDidentifyO canal não tem segredo de identificação configurado no painel.
EXPIREDidentifyO JWT já passou do exp.
INVALID_SIGNATUREidentifyA assinatura não bate com o segredo atual do canal (segredo errado, ou foi regenerado).
INVALID_TOKENidentifyToken malformado, sub ausente/inválido, exp ausente, vida útil maior que 1 hora, ou email/name inválidos.
SESSION_EXPIREDReconexão de sessão (WebSocket/REST)A sessão identificada expirou por inatividade (30 dias) ou foi encerrada por logout/novo identify em outro dispositivo. O widget volta a chamar identify ou cai para anônimo.

API de leitura servidor-a-servidor​

Para o backend da sua plataforma consultar o histórico de um usuário identificado — por exemplo, para mostrar as conversas dele dentro da sua própria área logada.

Autenticação​

Authorization: Bearer <segredo do canal>

Não é o mesmo mecanismo da API pública geral da Atalia (que usa x-api-key). É o mesmo segredo gerado no passo 1, comparado em tempo constante no servidor. Guarde-o como qualquer outro segredo de backend — nunca em código versionado ou exposto ao navegador.

Rate limit​

120 requisições por minuto, por canal.

Paginação​

Todos os endpoints de listagem aceitam ?page=1&limit=20 (limit máximo: 100, padrão: 20). A resposta segue o envelope:

{
"success": true,
"data": [ /* ... */ ],
"error": null,
"meta": { "page": 1, "limit": 20, "total": 42 }
}

GET /api/webchat/:token/identities/:external_id/conversations​

Lista as conversas do usuário identificado, mais recentes primeiro.

{
"success": true,
"data": [
{
"id": 8821,
"status": "open",
"created_at": "2026-09-20T14:03:00.000Z",
"updated_at": "2026-09-29T09:12:00.000Z",
"last_message_at": "2026-09-29T09:12:00.000Z",
"message_count": 14
}
],
"error": null,
"meta": { "page": 1, "limit": 20, "total": 3 }
}

GET /api/webchat/:token/identities/:external_id/conversations/:id/messages​

Lista as mensagens de uma conversa específica do usuário identificado, em ordem cronológica. Notas internas e mensagens técnicas (chamadas de ferramenta) nunca aparecem aqui.

{
"success": true,
"data": [
{
"id": 55012,
"direction": "in",
"sender_type": "customer",
"type": "text",
"content": "Qual o horário de atendimento?",
"media_url": null,
"created_at": "2026-09-29T09:10:00.000Z"
}
],
"error": null,
"meta": { "page": 1, "limit": 20, "total": 14 }
}

Respostas de erro​

HTTPcodeQuando
401INVALID_CHANNEL_SECRETBearer ausente, incorreto, ou canal sem segredo configurado.
404IDENTITY_NOT_FOUNDexternal_id sem identidade registrada nesse canal, ou a conversa/id não pertence a esse usuário/canal.
429RATE_LIMITEDMais de 120 requisições no minuto corrente para o canal.

A conversa de outro usuário ou de outro canal sempre devolve 404 IDENTITY_NOT_FOUND — nunca 403 — para não confirmar a existência do recurso a quem não tem acesso a ele.

Merge de contato​

Quando um visitante que já tinha uma conversa anônima no navegador é identificado pela primeira vez, a Atalia aproveita o histórico dele em vez de começar do zero:

  1. Se já existe uma identidade registrada para aquele sub neste canal, a conversa é vinculada ao contato dela.
  2. Senão, se o email do JWT bate com um contato já existente na empresa, o visitante é vinculado a esse contato (o e-mail é afirmado pelo seu backend, e por isso é tratado como confiável).
  3. Senão, se a sessão anônima atual do navegador já tinha um contato, esse contato anônimo é promovido a identificado.
  4. Senão, um contato novo é criado.

name e email do JWT sobrescrevem os dados do contato quando vêm preenchidos. Telefone e atributos personalizados nunca são tocados pelo identify.

Comportamento de logout​

AtaliaChat.logout() encerra a sessão identificada no servidor — o token anterior nunca mais autentica nada, nem REST nem WebSocket. Uma nova sessão anônima é iniciada a partir daí, sem acesso ao histórico do usuário que acabou de sair: é um visitante novo do ponto de vista do widget, mesmo que seja fisicamente a mesma pessoa no mesmo navegador.

Perguntas frequentes​

Por que identify não funciona?

Verifique, na ordem mais comum de causa:

  1. Segredo errado. O JWT foi assinado com um segredo diferente do que está configurado no canal agora (ex.: o segredo foi regenerado depois que o seu backend copiou o antigo). Sintoma: INVALID_SIGNATURE.
  2. exp maior que 1 hora. A Atalia recusa qualquer token com vida útil acima de 3600 segundos, mesmo que a assinatura esteja correta. Sintoma: INVALID_TOKEN.
  3. Relógio do servidor que assina desincronizado. Um relógio adiantado gera tokens "do futuro"; um relógio atrasado gera tokens que já nascem expirados. Sincronize por NTP. Sintoma: EXPIRED ou INVALID_TOKEN de forma intermitente.
  4. Nenhum segredo configurado no canal. Sintoma: NO_SECRET_CONFIGURED — gere o segredo em Editar canal › Instalação › Identificação de usuários.