Detectar compartilhamento de conta
Previna o compartilhamento não autorizado de contas e aplique limites de licenças. Marque a telemetria com a conta que está logada e leia a regra account_shared_detected a partir do seu backend para saber quando uma conta é usada por mais dispositivos do que você permite.
Um conjunto de credenciais, usado por mais pessoas do que pagaram por ele: a assinatura compartilhada, os dois assentos que silenciosamente atendem oito pessoas, a conta revendida. Nada disso parece incomum nos seus próprios logs, porque cada acesso é um login bem-sucedido com a senha certa.
O Device Intelligence adiciona o que falta aos logs: uma identidade de dispositivo em cada sessão. Marque a sua telemetria com a conta que está logando, e o veredito chega ao seu backend durante o próprio login. No painel você também vê a forma disso: quais dispositivos, logando de onde, se as sessões se sobrepuseram e se a conta pulou de região mais rápido do que qualquer pessoa consegue viajar.
O mesmo mecanismo aplica limites de licenças. Defina o limite para o número de assentos vendidos, e a regra avisa quando um cliente silenciosamente os ultrapassou.
Como a cside detecta o compartilhamento de conta
Marque a sua telemetria com o id da conta de quem está logado. A cside conta os dispositivos distintos vistos para aquela conta dentro de uma janela deslizante, e quando essa contagem ultrapassa o limite do seu site, a regra dispara.
Isso acontece em duas metades, e elas rodam em lugares diferentes:
- O seu navegador chama
sendClientTelemetry({ userId })e recebe um token de volta. - O seu servidor envia esse token para a cside e lê o veredito.
Mantenha a checagem do veredito no seu servidor. A sua chave de API de Device Intelligence não pode chegar perto do código do navegador, e um veredito que o navegador pudesse ler seria um veredito que o navegador poderia forjar.
O que você vai precisar
- Device Intelligence ativado no domínio
- O script instalado e
sendClientTelemetrysendo chamado: veja Configurar Device Intelligence - Uma chave de API de Device Intelligence de backend: veja Events API
- A regra ativada para o seu domínio. A ativação self-serve ainda não existe, então peça à cside para ativá-la

No navegador: envie o id da conta
Marque a telemetria com userId
Chame sendClientTelemetry uma vez no carregamento da página sem userId, para que a coleta comece cedo, depois chame novamente com userId anexado assim que souber quem está logado. Entregue o token que ela retorna diretamente ao seu próprio backend; o navegador não faz mais nada com ele.
Você pode chamá-la de novo sempre que quiser uma leitura atualizada, não só no login. Cada chamada produz uma nova avaliação, então em sessões de longa duração uma chamada periódica mantém a contagem de dispositivos atualizada, em vez de deixar você com um veredito de quando a pessoa fez login pela última vez.
import { initDeviceIntelligence } from "@cside.dev/device-intelligence";
const { sendClientTelemetry } = await initDeviceIntelligence({
teamID: "[your-team-id]",
});
// Call this at sign-in, and anywhere else you want to refresh the reading:
// long-running sessions benefit from a periodic call.
async function onSignIn(userId) {
const { token, errors } = await sendClientTelemetry({ userId });
if (errors) {
console.error(errors);
return;
}
// Your own backend route, not a cside endpoint. It does the actual
// verdict check server-side; see the next section.
await fetch("/api/account-sharing-check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
}userId é o padrão, não uma exigência. O que importa é que a chave que você envia e a chave que a regra lê sejam a mesma, escrita de forma idêntica. Enviar accountId ou user_id contra uma regra ainda configurada para userId não gera erro: o valor é lido como permanentemente ausente, e você verá outcome: 3 em toda avaliação, em vez de outcome: 1 ou outcome: 2. Se você já envia uma chave diferente, a cside pode apontar a regra para ela. Veja Já envia um id diferente?.
userId e todo outro id externo é armazenado e incluído em exportações. Envie um identificador de conta opaco, não um endereço de e-mail, nome, ou qualquer outra coisa que você não gostaria de persistir.
Já envia um id diferente?
Você não precisa trocar para userId. O que importa é que a regra leia a mesma chave que você envia, e a cside pode reconfigurar a regra, então nada muda no seu cliente.
A chave precisa ser uma de userId, accountId, organizationId ou teamId. Essas são as únicas quatro em que a regra pode se basear, porque a chave precisa de um campo com um conjunto limitado de valores. email, orderId, e qualquer outra coisa ainda podem ser enviados e armazenados, eles só não podem ser a chave, então envie um dos quatro junto com o que você já usa.
No seu servidor: leia o veredito
Esta é a rota /api/account-sharing-check do passo anterior. Ela troca o token pela avaliação completa e decide o que fazer, tudo no servidor, com a sua chave de API de Device Intelligence.
curl https://api.cside.com/token/v2/verify \
--request POST \
--header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
--header "Content-Type: application/json" \
--data '{"token":"'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'"}'
O endpoint retorna 202 Accepted com { "status": "pending" } enquanto a avaliação ainda está rodando, e depois 200 OK quando termina. Faça polling até cerca de 3 vezes, com um intervalo de aproximadamente 3 segundos como limite superior seguro: o motor de regras roda de forma síncrona como parte de armazenar o resultado, então o veredito costuma estar pronto já na primeira consulta. E ele está pronto por completo: rule_evaluations_v2 é preenchido no exato momento em que status se torna "completed", nunca depois, então, uma vez que você veja "completed", uma entrada ausente está genuinamente ausente, não a caminho ainda. Se você esgotar as tentativas e ainda estiver pendente, trate como se não houvesse veredito para esse login, em vez de bloquear a pessoa: o próximo login produz uma nova avaliação.
Aqui está tudo montado: troque o token, verifique se ele pertence à conta que está de fato logada, depois encontre a entrada de compartilhamento de conta e decida com base nela. Verifique tanto outcome quanto evaluated_as no próprio código, não apenas o slug da regra: outras regras, incluindo o experimento shadow de outra pessoa, podem viajar no mesmo array.
// apps/api-style illustrative route handler. Adjust to your own framework.
// The wire sends these as integers. Naming them keeps the check readable;
// the full set of values is in the tables below.
const OUTCOME_TRUE = 1;
const LIFECYCLE_ACTIVE = 3;
app.post("/api/account-sharing-check", async (req, res) => {
const { token } = req.body;
const evaluation = await verifyToken(token);
// Trust your own session, never the browser. The userId on the telemetry is
// whatever the page chose to send, so bind it to the signed-in account
// before you act on anything.
if (evaluation.fingerprint?.external_ids?.userId !== req.session.userId) {
return res.sendStatus(409);
}
const sharing = findAccountSharing(evaluation);
if (sharing !== undefined) {
// This account is over your site's device limit right now.
// Step up auth, cap sessions, flag for review: see "Decide what to do" below.
}
res.sendStatus(204);
});
async function verifyToken(token, attempt = 0) {
const response = await fetch("https://api.cside.com/token/v2/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CSIDE_FINGERPRINT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ token }),
});
if (!response.ok) {
// An expired key, a bad token, a rate limit. Never fall through: a failed
// request is an outage, not a verdict of "nobody is sharing this account".
throw new Error(`Device Intelligence verify failed: ${response.status}`);
}
const data = await response.json();
if (data.status === "pending" && attempt < 3) {
await new Promise((resolve) => setTimeout(resolve, 3000));
return verifyToken(token, attempt + 1);
}
if (data.status === "failed") {
// Evaluation itself failed. Don't treat this the same as "no entry":
// a failure isn't the healthy case.
throw new Error("Device Intelligence evaluation failed");
}
return data;
}
function findAccountSharing(data) {
// An absent entry means the rule wasn't evaluated, not that it
// evaluated to false.
return data.rule_evaluations_v2?.values?.find(
(v) =>
v.evaluation.rule_slug === "account_shared_detected" &&
v.evaluation.outcome === OUTCOME_TRUE &&
// Shadow rules ride in the same array. Acting on one would enforce
// an experiment that isn't live for your site.
v.evaluation.evaluated_as === LIFECYCLE_ACTIVE,
);
}
O userId de um token é o que quer que o navegador tenha enviado. Alguém pode omiti-lo, ou colocar ali uma conta diferente, então tratá-lo como prova de identidade permite que essa pessoa empurre observações para uma conta que não é dela, ou mantenha a própria conta parecendo limpa. Compare fingerprint.external_ids.userId com a conta na sua sessão de servidor e recuse qualquer coisa que não corresponda. A cside diz o que o navegador afirmou; só você sabe quem está de fato logado.
outcome e evaluated_as são inteiros, não strings. Toda regra avaliada e publicada relata uma entrada em cada evento, seja qual for o resultado em que ela cai: 1, 2 e 3 são estados estáveis que você vai ver continuamente, não ocorrências pontuais. 4 é a exceção, e significa algo mais específico do que parece: a regra precisa de uma medição que existe mas ainda não esquentou. Isso é diferente de um status de "pending", que significa que a própria avaliação ainda não terminou, e é para isso que serve o loop de polling acima. Fazer polling do mesmo token com mais insistência não vai transformar um 4 em um veredito; ele se resolve em avaliações posteriores conforme os dados se acumulam para aquela conta.
Mesmo enum da referência de rule_evaluations_v2; aqui está o que cada valor significa especificamente para esta regra:
outcome | Significado | Quando você vai observar |
|---|---|---|
0 | Não especificado | Não é produzido |
1 | Verdadeiro | Continuamente, enquanto a conta está acima do seu limite |
2 | Falso | Continuamente, o estado normal para uma conta saudável que não está acima do limite |
3 | Ausente (estrutural) | Continuamente, se a regra não conseguir ler userId da sua telemetria, por exemplo uma chave errada |
4 | Pendente (transitório) | Quando a medição da regra ainda não esquentou. Não é o mesmo que status: "pending", e fazer polling desse token de novo não resolve isso |
evaluated_as | Significado |
|---|---|
1 | Rascunho |
2 | Shadow: experimento de outra pessoa, pode aparecer a qualquer momento, não aja sobre ele |
3 | Ativo: a regra que o seu site ligou |
4 | Arquivado |
Uma observação sobre transition, útil se você preferir agir uma vez em vez de a cada requisição: uma vez que você já verificou outcome: 1, transition: 3 significa que a conta acabou de ultrapassar o limite nesta avaliação, e transition: 2 significa que ela já está acima dele desde uma avaliação anterior.
A conta que disparou a regra está em fingerprint.external_ids na mesma resposta, não em evaluation.key. key é um hash do que a regra usou como chave e nunca carrega o id da conta em texto plano. Note também que key.key_hash é uma string JSON, não um número, e que este corpo de resposta mistura dois formatos de timestamp: created_at e updated_at no nível superior são números em milissegundos, enquanto evaluated_at e event_time dentro da avaliação são objetos { seconds, nanos }.
Exemplo de resposta reduzido, capturado de uma avaliação real, com outros campos omitidos. Repare na segunda entrada: outras regras realmente viajam no mesmo array, e é por isso que o código acima combina o slug, outcome e evaluated_as juntos, não apenas o slug.
{
"status": "completed",
"created_at": 1789731723590,
"updated_at": 1789731723590,
"fingerprint": {
"device_id": "dev_3f8a21c9",
"external_ids": {
"userId": "acct_9f2a1c"
}
},
"rule_evaluations_v2": {
"values": [
{
"evaluation": {
"rule_slug": "account_shared_detected",
"outcome": 1,
"evaluated_as": 3,
"transition": 3,
"key": { "key_hash": "16386830586737169487" },
"event_time": { "seconds": 1789731723, "nanos": 590000000 },
"evaluated_at": { "seconds": 1789731723, "nanos": 605000000 }
}
},
{
"evaluation": {
"rule_slug": "multi_account_detected",
"outcome": 2,
"evaluated_as": 3,
"transition": 2,
"key": { "key_hash": "261153104274697293" },
"event_time": { "seconds": 1789731723, "nanos": 590000000 },
"evaluated_at": { "seconds": 1789731723, "nanos": 605000000 }
}
}
]
}
}
Não mostrado acima: alguns objetos aninhados na resposta completa usam nomes de campo em camelCase em vez de snake_case, onde compartilham um formato de transmissão com o designer de regras do painel (dentro de explanation, e key.components[].dimension). Se você imprimir a sua própria resposta completa e ver uma mistura dos dois, isso é esperado, não um bug na sua integração.
Veja a referência completa de campos de rule_evaluations_v2 para o restante dos campos.
Decida o que fazer a respeito
A cside relata, não bloqueia. O que você faz com uma regra disparada é decisão sua: escalar a autenticação, limitar sessões simultâneas para a conta, ou marcar para revisão manual.
Confirme que está funcionando
Primeiro, confirme que o id está realmente chegando: troque um token e verifique se fingerprint.external_ids contém o valor que você espera sob userId.
Depois verifique a própria regra, antes que alguém compartilhe qualquer coisa. Como toda regra avaliada e publicada relata em cada evento, você não precisa disparar um compartilhamento para ver se o pipeline funciona:
outcome: 2na entradaaccount_shared_detectedé como se parece uma integração saudável e corretamente configurada: a regra está rodando, lendo a chave certa, e ninguém está acima do limite. Esta é a sua confirmação negativa, e você vai vê-la continuamente, não só uma vez.outcome: 3significa que você está enviando a chave errada. Corrija agora, antes de testar mais; veja Já envia um id diferente?.- Nenhuma entrada para
account_shared_detectedsignifica que a regra ainda não foi ativada para este domínio, é uma regra apenas de medição que não publica, ou a avaliação não rodou para este evento. Pergunte à cside.
Veja virar verdadeiro
Depois de confirmar a linha de base saudável outcome: 2 acima, dispare a regra de propósito para ver o fluxo completo, incluindo a transição:
- Faça login em uma conta de teste a partir de três navegadores ou perfis de dispositivo diferentes, dentro de um intervalo de 2 horas. Esses são os padrões de fábrica; se o limite ou a janela do seu site foram reconfigurados, use os seus
- Troque um token de sessão depois de cada login e observe
account_shared_detected: ele permaneceoutcome: 2para os dois primeiros, depois muda paraoutcome: 1(comtransition: 3) assim que o terceiro dispositivo coloca a conta acima do limite do seu site - Confirme
evaluated_as: 3junto com isso, para saber que você está lendo a regra ativa e não um experimento shadow
Janelas de navegação privada na mesma máquina física podem não ser lidas como dispositivos distintos, então use navegadores ou perfis de dispositivo separados em vez de várias janelas privadas em um único navegador.
curl https://api.cside.com/token/v2/verify \
--request POST \
--header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
--header "Content-Type: application/json" \
--data '{"token":"'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'"}'
# rule_evaluations_v2 carries the rule outcomes, account_shared_detected among them.
# If "status" is "pending", wait a few seconds and retry, up to about 3 times.Depois de confirmar isso na API, abra essa sessão no painel da cside: sua página de detalhes da sessão tem uma aba Identity graph que mostra a mesma coisa visualmente, uma conta se ramificando em vários nós de dispositivo.

Se o seu time tem acesso à página do caso de uso de compartilhamento de conta no painel, o seu tile de cobertura é a forma mais direta de pegar uma chave errada: ele mede que fração dos seus eventos realmente carrega o id que a regra lê. Essa página ainda está em acesso antecipado, então pode não estar ativada para você.

Próximos passos
Solução de problemas
Quase toda integração que não vê nada tem a mesma causa: o id externo que você envia não é o que a regra lê. userId é o padrão em vez de uma exigência, então a cside pode reconfigurar a regra para qualquer um dos outros campos que podem servir de chave. A sua telemetria e a regra só precisam concordar sobre um deles.
- Verifique o que chegou. Troque um token e olhe
fingerprint.external_ids. Se o id da sua conta não estiver lá,sendClientTelemetryrodou sem ele, ou rodou antes de você saber quem estava logando. - Verifique se a regra conseguiu lê-lo. Um
outcomede3na entradaaccount_shared_detectedsignifica que a regra rodou e não conseguiu encontrar a sua chave. - Verifique se o valor é estável. Um id de sessão ou um token rotativo faz cada login parecer uma conta nova, então nada nunca se acumula em uma só.
- Reconfigure em vez de reescrever. Peça à cside para apontar a regra para a sua chave. Veja Já envia um id diferente?.
Thanks for your feedback!