Identificação de usuários no Webchat
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).
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
- Acesse Configurações › Canais, abra o canal de webchat desejado e vá em Editar canal.
- Na aba Instalação, seção Identificação de usuários, clique em Gerar segredo. O segredo é criado na hora, sem precisar salvar.
- 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.
- (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 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):
| Claim | Obrigatório | Descrição |
|---|---|---|
sub | sim | Identificador único e estável do usuário na sua plataforma (external_id). 1–255 caracteres. |
exp | sim | Expiração. Vida útil máxima de 1 hora a partir da emissão — tokens com exp maior são rejeitados. |
name | não | Nome do usuário. Sobrescreve o nome do contato na Atalia quando presente. |
email | não | E-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');
}
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).
| Evento | Quando dispara | Payload |
|---|---|---|
ready | Widget carregado e pronto para uso | — |
identified | identify concluído com sucesso | { externalId, contactId } |
logout | Sessão identificada encerrada (chamada explícita ou expiração) | — |
conversation:started | Uma nova conversa foi aberta pelo visitante | { conversationId } |
conversation:closed | A conversa atual foi encerrada | { conversationId } |
message:received | Mensagem nova recebida no widget (sem o conteúdo — só o identificador) | { conversationId, messageId } |
unread:changed | Contador 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ódigo | Onde aparece | Significado |
|---|---|---|
NO_SECRET_CONFIGURED | identify | O canal não tem segredo de identificação configurado no painel. |
EXPIRED | identify | O JWT já passou do exp. |
INVALID_SIGNATURE | identify | A assinatura não bate com o segredo atual do canal (segredo errado, ou foi regenerado). |
INVALID_TOKEN | identify | Token malformado, sub ausente/inválido, exp ausente, vida útil maior que 1 hora, ou email/name inválidos. |
SESSION_EXPIRED | Reconexã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
| HTTP | code | Quando |
|---|---|---|
| 401 | INVALID_CHANNEL_SECRET | Bearer ausente, incorreto, ou canal sem segredo configurado. |
| 404 | IDENTITY_NOT_FOUND | external_id sem identidade registrada nesse canal, ou a conversa/id não pertence a esse usuário/canal. |
| 429 | RATE_LIMITED | Mais 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:
- Se já existe uma identidade registrada para aquele
subneste canal, a conversa é vinculada ao contato dela. - Senão, se o
emaildo 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). - Senão, se a sessão anônima atual do navegador já tinha um contato, esse contato anônimo é promovido a identificado.
- 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:
- 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. expmaior que 1 hora. A Atalia recusa qualquer token com vida útil acima de 3600 segundos, mesmo que a assinatura esteja correta. Sintoma:INVALID_TOKEN.- 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:
EXPIREDouINVALID_TOKENde forma intermitente. - Nenhum segredo configurado no canal. Sintoma:
NO_SECRET_CONFIGURED— gere o segredo em Editar canal › Instalação › Identificação de usuários.