Quando usar
- O usuário abre o seu site em múltiplas abas e você quer que o widget reconheça a mesma identidade em todas
- Sua aplicação permite múltiplas abas abertas simultaneamente
- Você já usa (ou quer usar) Validação de Identidade do widget e quer evitar exigir login em cada aba
Pré-requisitos
- Web Widget já configurado com Validação de Identidade (Identity Validation) habilitada
user_idúnico por usuário no seu sistema- Backend disponível para gerar
identifier_hash(opção recomendada) - Web Widget instalado — ver Como instalar o Web Widget do Cloud Chat no site

Sobre este artigo
Este guia explica como manter a mesma sessão de usuário do Web Widget Cloud Chat em duas ou mais abas, sem exigir que cada aba esteja autenticada.
Cenário típico: o usuário abre o site em duas abas e inicia uma conversa pelo widget em uma delas. Na segunda aba, o widget deve reconhecer o mesmo usuário e exibir o mesmo histórico de conversas.
Como funciona (explicação simplificada)
-
user_id= "nome no crachá" — identifica quem é o usuário -
identifier_hash= "carimbo de autenticidade" — prova que o crachá é verdadeiro -
O Cloud Chat confere o carimbo (usando uma chave secreta que só o servidor conhece) antes de liberar acesso ao histórico
Fluxo entre abas

Ponto-chave: cada aba precisa chamar setUser() independentemente. O cookie cw_user_* é compartilhado automaticamente entre abas (mesmo domínio), mas o authToken interno do widget é por instância (por aba). Por isso, ambas as abas precisam ter acesso ao user_id e identifier_hash para inicializar corretamente.
Opção recomendada: Endpoint server-side
Esta é a opção mais segura. O identifier_hash nunca é armazenado no navegador — é obtido sob demanda a partir de um endpoint protegido pela autenticação da sua aplicação.
Backend (Node.js / Express)
const crypto = require('crypto');
app.get('/api/widget-identity', authMiddleware, (req, res) => {
const userId = String(req.user.id);
const hmacToken = process.env.CLOUDCHAT_HMAC_TOKEN;
const identifierHash = crypto
.createHmac('sha256', hmacToken)
.update(userId)
.digest('hex');
res.json({
user_id: userId,
identifier_hash: identifierHash,
name: req.user.name,
email: req.user.email,
});
});
Backend (Ruby on Rails)
class Api::WidgetIdentityController < ApplicationController
before_action :authenticate_user!
def show
user_id = current_user.id.to_s
hmac_token = ENV['CLOUDCHAT_HMAC_TOKEN']
identifier_hash = OpenSSL::HMAC.hexdigest('sha256', hmac_token, user_id)
render json: {
user_id: user_id,
identifier_hash: identifier_hash,
name: current_user.name,
email: current_user.email
}
end
end
Backend (Python / Django)
import hmac
import hashlib
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.conf import settings
@login_required
def widget_identity(request):
user_id = str(request.user.id)
identifier_hash = hmac.new(
settings.CLOUDCHAT_HMAC_TOKEN.encode(),
user_id.encode(),
hashlib.sha256
).hexdigest()
return JsonResponse({
'user_id': user_id,
'identifier_hash': identifier_hash,
'name': request.user.get_full_name(),
'email': request.user.email,
})
Frontend (qualquer aba)
<script>
window.cloudchatSettings = {
// ... configurações do widget
};
window.addEventListener('cloudchat:ready', async function () {
try {
const response = await fetch('/api/widget-identity', {
credentials: 'include',
});
if (!response.ok) {
console.error('Falha ao obter identidade do widget');
return;
}
const { user_id, identifier_hash, name, email } = await response.json();
window.$cloudchat.setUser(user_id, {
identifier_hash: identifier_hash,
name: name,
email: email,
});
} catch (error) {
console.error('Erro ao configurar usuário do widget:', error);
}
});
</script>
Vantagens:
-
O
hmac_tokennunca sai do servidor -
O
identifier_hashnunca é persistido no navegador -
Cada aba obtém dados frescos, garantindo consistência
-
Logout retorna 401, e o widget não é identificado
Opção alternativa: localStorage (com ressalvas)
Use somente se sua aplicação não tem backend disponível para fornecer o identifier_hash (SPAs puramente estáticas).
Aviso de segurança: o localStorage é acessível por qualquer script JavaScript no mesmo domínio. Se o site for vulnerável a XSS, um atacante pode ler o identifier_hash armazenado.
Aba que gera os dados (ex: página de login)
const userData = {
user_id: '12345',
identifier_hash: 'abc123def456...',
name: 'Maria Silva',
email: '[email protected]',
};
localStorage.setItem('cloudchat_user', JSON.stringify(userData));
Qualquer aba que carrega o widget
window.addEventListener('cloudchat:ready', function () {
const stored = localStorage.getItem('cloudchat_user');
if (!stored) return;
try {
const { user_id, identifier_hash, name, email } = JSON.parse(stored);
window.$cloudchat.setUser(user_id, {
identifier_hash,
name,
email,
});
} catch (e) {
console.error('Dados do widget inválidos no localStorage');
localStorage.removeItem('cloudchat_user');
}
});
Ao fazer logout
localStorage.removeItem('cloudchat_user');
window.$cloudchat.reset();
Opção avançada: sessionStorage + BroadcastChannel
Combina a não persistência do sessionStorage com sincronização entre abas via BroadcastChannel API. Os dados existem apenas enquanto pelo menos uma aba está aberta.
Inicialização (em todas as abas)
const CHANNEL_NAME = 'cloudchat-session';
const STORAGE_KEY = 'cloudchat_user';
const channel = new BroadcastChannel(CHANNEL_NAME);
channel.onmessage = (event) => {
if (event.data.type === 'session-data') {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(event.data.payload));
initWidget(event.data.payload);
}
if (event.data.type === 'session-request') {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
channel.postMessage({ type: 'session-data', payload: JSON.parse(stored) });
}
}
if (event.data.type === 'session-clear') {
sessionStorage.removeItem(STORAGE_KEY);
window.$cloudchat.reset();
}
};
function initWidget(userData) {
window.$cloudchat.setUser(userData.user_id, {
identifier_hash: userData.identifier_hash,
name: userData.name,
email: userData.email,
});
}
window.addEventListener('cloudchat:ready', function () {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
initWidget(JSON.parse(stored));
return;
}
channel.postMessage({ type: 'session-request' });
// Timeout: se nenhuma aba responder em 2s, esta é a primeira aba
});
Ao autenticar (aba principal)
const userData = { user_id, identifier_hash, name, email };
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(userData));
channel.postMessage({ type: 'session-data', payload: userData });
initWidget(userData);
Ao fazer logout
sessionStorage.removeItem(STORAGE_KEY);
channel.postMessage({ type: 'session-clear' });
window.$cloudchat.reset();
Vantagens:
-
Dados não persistem após fechar todas as abas
-
Sincronização em tempo real entre abas
-
Sem dependência de cookies acessíveis por JS
Limitações:
-
BroadcastChannelnão é suportado no IE11 (mas funciona em todos os navegadores modernos) -
Se o usuário fechar todas as abas, precisa autenticar novamente
Segurança
O que NUNCA fazer
Nunca exponha o hmac_token (chave secreta) no frontend. É a chave que o servidor usa para gerar o identifier_hash. Se um atacante a obtém, pode personificar qualquer usuário.
// ❌ ERRADO
const hmacToken = 'sua-chave-secreta-aqui';
const hash = CryptoJS.HmacSHA256(userId, hmacToken).toString();
Riscos de armazenar identifier_hash no localStorage

Contextualização do risco
O identifier_hash por si só não é um segredo de alto valor:
-
É uma assinatura (HMAC) do
user_id, não um token de acesso -
Um atacante precisa do
user_idcorrespondente E da validação HMAC ativa para personificar alguém -
Não concede acesso a sistemas além do widget de chat
-
O impacto de vazamento é limitado: o atacante poderia, no máximo, abrir conversas no widget como aquele usuário
Ainda assim, a opção server-side é preferível por eliminar a exposição.
Tabela comparativa de armazenamento

Boas práticas
-
Proteja-se contra XSS — é a ameaça nº 1 para qualquer dado no navegador. Use Content Security Policy (CSP), sanitize inputs, mantenha dependências atualizadas
-
Gere
identifier_hashno servidor, nunca no frontend -
Use HTTPS sempre — sem exceções
-
Limite a superfície de ataque — carregue o widget apenas em páginas necessárias
-
Limpe dados ao fazer logout — remova
localStorage/sessionStoragee chame$cloudchat.reset()
Referências:
Reset de sessão
Use window.$cloudchat.reset() para limpar a sessão. Internamente:
-
Fecha o chat se estiver aberto
-
Remove o cookie
cw_conversation(token de sessão da conversa) -
Remove o cookie
cw_user_{websiteToken}(identidade do usuário) -
Recarrega o iframe do widget, iniciando sessão limpa
Quando usar

Exemplo: logout completo
function onLogout() {
// 1. Limpar dados armazenados (se localStorage)
localStorage.removeItem('cloudchat_user');
// 2. Resetar o widget
if (window.$cloudchat) {
window.$cloudchat.reset();
}
// 3. Redirecionar para página de login
window.location.href = '/login';
}
Troubleshooting
Problema: Widget mostra "Iniciar nova conversa" em vez do histórico
Causa provável: setUser() não foi chamado, ou foi chamado com identifier_hash inválido.
Solução:
-
Verifique no console se há erros de rede (HTTP 401 no endpoint
/widget/contact) -
Confirme que o
identifier_hashfoi gerado com ohmac_tokencorreto -
Confirme que o
user_idpassado parasetUser()é o mesmo usado para gerar o hash
Problema: Abas mostram usuários diferentes
Causa provável: cada aba está chamando setUser() com dados diferentes.
Solução:
-
Endpoint server-side: confirme que ambas as abas chamam o mesmo endpoint e usuário logado
-
localStorage: verifique se os dados foram gravados antes da segunda aba ler
Problema: Após logout, widget ainda mostra dados do usuário anterior
Causa provável: $cloudchat.reset() não foi chamado, ou os cookies não foram limpos.
Solução:
-
Chame
window.$cloudchat.reset()antes ou durante o logout -
Limpe localStorage/sessionStorage se estiver armazenando dados lá
-
Verifique se o cookie
cw_user_*foi removido (DevTools → Application → Cookies)
Problema: setUser() lança erro "Identifier should be a string or a number"
Causa: o user_id está como undefined, null ou objeto.
Solução: garanta que é string ou número:
window.$cloudchat.setUser(String(userData.user_id), { ... });
Problema: setUser() lança erro "User object should have one of the keys..."
Causa: o objeto não contém nenhuma das chaves obrigatórias.
Solução: inclua ao menos uma de name, email ou avatar_url:
window.$cloudchat.setUser(userId, {
identifier_hash: hash,
name: 'Nome do Usuário',
});
Problema: BroadcastChannel não sincroniza entre abas
Causa provável: abas em domínios diferentes (ex: app.exemplo.com vs www.exemplo.com).
Solução: BroadcastChannel só funciona entre páginas do mesmo domínio (same-origin). Garanta que todas as abas usam exatamente a mesma URL base.
Observações
-
Para a instalação básica do Web Widget: Como instalar o Web Widget do Cloud Chat no site
-
Para múltiplas conversas e histórico (que essa feature pressupõe): Como ativar múltiplas conversas e histórico no Web Widget
-
Para uso em apps mobile via WebView: Como integrar o Web Widget em aplicativos mobile