Accountdeling detecteren
Voorkom ongeautoriseerd delen van accounts en handhaaf license seats. Tag telemetry met het account dat is ingelogd, en lees vervolgens de regel account_shared_detected vanuit uw backend om te zien wanneer één account vanaf meer apparaten wordt gebruikt dan u toestaat.
Eén set inloggegevens, gebruikt door meer mensen dan ervoor betaald hebben: het gedeelde abonnement, de twee seats waarop in stilte acht mensen draaien, het doorverkochte account. Niets daarvan valt op in uw eigen logs, want elke login is een geslaagde login met het juiste wachtwoord.
Device Intelligence voegt toe wat de logs missen: een apparaatidentiteit bij elke sessie. Tag uw telemetry met het account dat inlogt, en het oordeel bereikt uw backend tijdens het inloggen zelf. In het dashboard krijgt u ook de vorm ervan te zien: welke apparaten, van waar ze inloggen, of sessies elkaar overlapten, en of het account sneller van regio wisselde dan iemand kan reizen.
Hetzelfde mechanisme handhaaft license seats. Stel de limiet in op het aantal verkochte seats, en de regel laat u weten wanneer een klant er in stilte overheen is gegroeid.
Hoe cside accountdeling detecteert
Tag uw telemetry met de account-id van wie er is ingelogd. cside telt het aantal unieke apparaten dat voor dat account is gezien binnen een voortschrijdend venster, en zodra dat aantal de limiet van uw site overschrijdt, gaat de regel af.
Dit zijn twee helften, en ze draaien op verschillende plekken:
- Uw browser roept
sendClientTelemetry({ userId })aan en krijgt een token terug. - Uw server stuurt dat token naar cside en leest het oordeel.
Houd de oordeelscontrole op uw server. Uw Device Intelligence API-key mag niet in de buurt van browsercode komen, en een oordeel dat de browser kan lezen, is een oordeel dat de browser kan vervalsen.
Wat u nodig heeft
- Device Intelligence ingeschakeld op het domein
- Het script geïnstalleerd en
sendClientTelemetrywordt aangeroepen: zie Device Intelligence instellen - Een backend Device Intelligence API-key: zie Events API
- De regel ingeschakeld voor uw domein. Self-serve activatie bestaat nog niet, dus vraag cside om deze in te schakelen

In de browser: stuur de account-id
Tag telemetry met userId
Roep sendClientTelemetry eenmaal aan bij het laden van de pagina, zonder userId, zodat de verzameling vroeg begint, en roep het daarna nogmaals aan met userId zodra u weet wie er is ingelogd. Geef het token dat wordt teruggegeven direct door aan uw eigen backend; de browser doet er verder niets mee.
U kunt het opnieuw aanroepen wanneer u een verse meting wilt, niet alleen bij het inloggen. Elke aanroep levert een nieuwe evaluatie op, dus bij langlopende sessies houdt een periodieke aanroep het apparaataantal actueel in plaats van u achter te laten met een oordeel van het moment waarop de persoon voor het laatst inlogde.
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 is de standaard, geen vereiste. Wat telt, is dat de key die u stuurt en de key die de regel leest, identiek gespeld zijn. Stuurt u accountId of user_id naar een regel die nog op userId staat ingesteld, dan is dat geen fout: de waarde wordt permanent als ontbrekend gelezen, en u ziet bij elke evaluatie outcome: 3 in plaats van outcome: 1 of outcome: 2. Stuurt u al een andere key, dan kan cside de regel daar in plaats daarvan op richten. Zie Stuurt u al een andere id?.
userId en elke andere external id worden opgeslagen en meegenomen in exports. Stuur een ondoorzichtige account-identifier, geen e-mailadres, naam of iets anders wat u niet zou willen bewaren.
Stuurt u al een andere id?
U hoeft niet over te stappen op userId. Wat telt, is dat de regel dezelfde key leest als die u stuurt, en cside kan de regel omkoppelen, dus er verandert niets in uw client.
De key moet wel een van userId, accountId, organizationId of teamId zijn. Dat zijn de enige vier waarop de regel kan keyen, omdat keyen een veld met een begrensde set waarden vereist. email, orderId en al het andere kunt u nog steeds sturen en opslaan, alleen kunnen ze niet de key zijn, dus stuur een van de vier mee naast wat u al gebruikt.
Op uw server: lees het oordeel
Dit is de route /api/account-sharing-check uit de vorige stap. Deze wisselt het token in voor de volledige evaluatie en bepaalt wat er gebeurt, volledig server-side, met uw Device Intelligence API-key.
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"'"}'
Het endpoint retourneert 202 Accepted met { "status": "pending" } zolang de evaluatie nog loopt, en daarna 200 OK zodra deze klaar is. Poll ongeveer 3 keer, met zo’n 3 seconden ertussen als veilige bovengrens: de rule engine draait synchroon als onderdeel van het opslaan van het resultaat, dus het oordeel is doorgaans al bij uw eerste poll klaar. En het is volledig klaar: rule_evaluations_v2 is gevuld op het exacte moment dat status "completed" wordt, nooit later, dus zodra u "completed" ziet, is een ontbrekende entry echt afwezig, niet nog onderweg. Loopt u uit uw pogingen en staat het nog op pending, behandel het dan als geen oordeel voor deze login in plaats van de persoon te blokkeren: de volgende login levert een verse evaluatie op.
Zo ziet het er helemaal bedraad uit: wissel het token in, controleer of het bij het account hoort dat daadwerkelijk is ingelogd, en zoek dan de entry voor accountdeling op en vertak daarop. Controleer zowel outcome als evaluated_as in de code zelf, niet alleen de regel-slug: andere regels, inclusief het schaduwexperiment van iemand anders, kunnen in dezelfde array meerijden.
// 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,
);
}
De userId op een token is wat de browser heeft meegestuurd. Iemand kan die weglaten, of er een ander account in zetten, dus als u het als identiteitsbewijs behandelt, kan die persoon waarnemingen op een account duwen dat niet van hem is, of zijn eigen account schoon laten lijken. Vergelijk fingerprint.external_ids.userId met het account in uw serversessie en weiger alles dat niet overeenkomt. cside vertelt u wat de browser beweerde; alleen u weet wie er daadwerkelijk is ingelogd.
outcome en evaluated_as zijn integers, geen strings. Elke geëvalueerde, gepubliceerde regel rapporteert bij elk event een entry, met welke outcome dan ook: 1, 2 en 3 zijn stabiele toestanden die u continu zult zien, geen eenmalige uitschieters. 4 is de uitzondering, en betekent iets specifiekers dan het lijkt: de regel heeft een meting nodig die wel bestaat, maar nog niet is opgewarmd. Dat is iets anders dan een status van "pending", wat betekent dat de evaluatie zelf nog niet klaar is en waar de pollinglus hierboven voor dient. Hetzelfde token harder pollen maakt van een 4 geen oordeel; die verdwijnt bij latere evaluaties naarmate er data voor dat account wordt opgebouwd.
Dezelfde enum als de rule_evaluations_v2-referentie; hier is wat elke waarde voor deze specifieke regel betekent:
outcome | Betekenis | Wanneer u dit ziet |
|---|---|---|
0 | Niet gespecificeerd | Wordt niet geproduceerd |
1 | True | Continu, zolang het account boven uw limiet zit |
2 | False | Continu, de normale toestand voor een gezond account dat niet boven de limiet zit |
3 | Ontbrekend (structureel) | Continu, als de regel userId niet uit uw telemetry kan lezen, bijvoorbeeld door een verkeerde key |
4 | Pending (tijdelijk) | Wanneer de meting van de regel nog niet is opgewarmd. Niet hetzelfde als status: "pending", en dit token opnieuw pollen maakt het niet ongedaan |
evaluated_as | Betekenis |
|---|---|
1 | Draft |
2 | Shadow: iemands experiment, kan op elk moment verschijnen, onderneem er geen actie op |
3 | Active: de regel die uw site heeft ingeschakeld |
4 | Archived |
Nog één ding over transition, handig als u liever eenmalig dan bij elk request iets doet: zodra u al outcome: 1 heeft gecontroleerd, betekent transition: 3 dat het account bij deze evaluatie net over de drempel is gegaan, en transition: 2 dat het al sinds een eerdere evaluatie over de drempel zit.
Het account dat de regel triggerde, staat in dezelfde response onder fingerprint.external_ids, niet onder evaluation.key. key is een hash van waarop de regel keyde en bevat nooit de account-id in platte tekst. Let ook op dat key.key_hash een JSON-string is, geen getal, en dat deze responsebody twee tijdstempelvormen mengt: created_at en updated_at op het hoogste niveau zijn getallen in milliseconden, terwijl evaluated_at en event_time binnen de evaluatie { seconds, nanos }-objecten zijn.
Ingekort voorbeeld van een response, vastgelegd uit een echte evaluatie, overige velden weggelaten. Let op de tweede entry: andere regels rijden echt mee in dezelfde array, en daarom matcht de code hierboven op slug, outcome en evaluated_as samen, niet op slug alleen.
{
"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 }
}
}
]
}
}
Niet hierboven getoond: sommige geneste objecten in de volledige response gebruiken camelCase-veldnamen in plaats van snake_case, waar ze een wireformat delen met de rule designer van het dashboard (binnen explanation, en key.components[].dimension). Als u uw eigen volledige response afdrukt en een mix van beide ziet, is dat te verwachten, geen bug in uw integratie.
Zie de volledige rule_evaluations_v2-veldreferentie voor de rest van de velden.
Bepaal wat u ermee doet
cside rapporteert, het blokkeert niet. Wat u doet met een afgaande regel, is aan u: stap over op sterkere authenticatie, beperk gelijktijdige sessies voor het account, of markeer het voor handmatige beoordeling.
Bevestig dat het werkt
Controleer eerst of de id daadwerkelijk binnenkomt: wissel één token in en controleer of fingerprint.external_ids de verwachte waarde onder userId bevat.
Controleer dan de regel zelf, voordat iemand ook maar iets deelt. Omdat elke geëvalueerde, gepubliceerde regel bij elk event rapporteert, hoeft u geen deling te veroorzaken om te zien of de pipeline werkt:
outcome: 2op de entryaccount_shared_detectedis hoe een gezonde, correct bedrade integratie eruitziet: de regel draait, leest de juiste key, en niemand zit boven de limiet. Dit is uw negatieve bevestiging, en u zult het continu zien, niet slechts eenmalig.outcome: 3betekent dat u de verkeerde key stuurt. Los dit nu op, voordat u verder test; zie Stuurt u al een andere id?.- Helemaal geen entry voor
account_shared_detectedbetekent dat de regel nog niet is geactiveerd voor dit domein, dat het een meet-only regel is die niet publiceert, of dat er voor dit event geen evaluatie liep. Vraag het cside.
Zie het omslaan naar true
Zodra u de gezonde outcome: 2-basislijn hierboven heeft bevestigd, trigger de regel dan opzettelijk om de volledige flow te zien, inclusief de transitie:
- Log in op één testaccount vanaf drie verschillende browsers of apparaatprofielen, binnen een tijdsspanne van 2 uur. Dat zijn de standaardwaarden zoals uitgeleverd; als de limiet of het venster van uw site is aangepast, gebruik dan die van uzelf
- Wissel na elke login een sessietoken in en kijk naar
account_shared_detected: het blijftoutcome: 2voor de eerste twee, en slaat om naaroutcome: 1(mettransition: 3) zodra het derde apparaat het account boven de limiet van uw site brengt - Bevestig
evaluated_as: 3ernaast, zodat u weet dat u de actieve regel leest en geen schaduwexperiment
Privénavigatievensters op dezelfde fysieke machine worden mogelijk niet als afzonderlijke apparaten gelezen, gebruik dus aparte browsers of apparaatprofielen in plaats van meerdere privévensters in één browser.
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.Zodra u het in de API heeft bevestigd, open die sessie dan in het cside-dashboard: de sessiedetailpagina heeft een tabblad Identity graph dat hetzelfde visueel laat zien, één account dat uitwaaiert naar meerdere apparaatnodes.

Als uw team toegang heeft tot de use case-pagina voor accountdeling in het dashboard, is de coverage tile de meest directe manier om een verkeerde key op te sporen: die meet welk deel van uw events daadwerkelijk de id bevat die de regel leest. Die pagina is nog in early access, dus mogelijk staat deze bij u nog niet aan.

Volgende stappen
Problemen oplossen
Vrijwel elke integratie die niets ziet, heeft dezelfde oorzaak: de external id die u stuurt, is niet de id die de regel leest. userId is de standaard, geen vereiste, dus cside kan de regel omkoppelen naar elk van de andere keybare velden. Uw telemetry en de regel hoeven het alleen met elkaar eens te zijn over één veld.
- Controleer wat er binnenkwam. Wissel een token in en kijk naar
fingerprint.external_ids. Staat uw account-id daar niet bij, dan liepsendClientTelemetryzonder deze, of vond de aanroep plaats voordat u wist wie er inlogde. - Controleer of de regel het kon lezen. Een
outcomevan3op de entryaccount_shared_detectedbetekent dat de regel draaide maar zijn key niet kon vinden. - Controleer of de waarde stabiel is. Een sessie-id of een roterend token laat elke login als een nieuw account overkomen, waardoor er nooit iets tegen één account wordt opgebouwd.
- Koppel om in plaats van te herschrijven. Vraag cside om de regel op uw key te richten. Zie Stuurt u al een andere id?.
Thanks for your feedback!