Skip to main content
Integration flow
Language

Events API

Integrate cside Device Intelligence into your application. Collect browser signals, generate session tokens, and retrieve detailed visitor intelligence from your backend.

This guide walks you through integrating the cside Device Intelligence Events API end-to-end: loading the client script, generating a session token, sending it to your backend, and interpreting the response payload.

Integration flow

Add the script to your page

If your app is built with a bundler, install the npm package and call initDeviceIntelligence({ teamID }) as early as your app allows. Otherwise, include the fail-open browser bootstrap at the top of your page’s <head>, before other application scripts, and replace its script.src value with the team-specific URL from the cside dashboard. The bootstrap loads client.js asynchronously, queues early calls, and rejects them if cside fails or times out so the page remains usable.

Submit telemetry and receive a session token

Once the script has loaded, a global sendClientTelemetry function becomes available (the npm package returns the same function from initDeviceIntelligence). Loading the script does not create a session fingerprint by itself. Call sendClientTelemetry(externalIds?) to submit telemetry to https://edge.csidefd.com/client and receive a session token. Pass an optional externalIds object if you want to attach your own identifiers.

const result = await sendClientTelemetry({
  email: "user@example.com",
  userId: "1234567890",
});

// Or call with no arguments
// const result = await sendClientTelemetry();

if (result.errors) {
  console.error(result.errors);
  throw new Error("Telemetry request failed.");
}

const token = result.token;

Parameters

ParameterTypeRequiredDescription
externalIdsRecord<string, string>NoOptional identifiers you want attached to the fingerprint, such as userId, orderId, paymentCardId, or email.

To receive the disposable email signal (fingerprint_enrichment.throwaway_email_detected on /token/v2/verify), pass the email address in this object using the key email. The account rules and the Identity graph key on userId (the Identity graph also accepts teamId or customerId), and the card testing rule keys on paymentCardId; see Retrieving datapoints for the format.

Return value

The function returns a JSON object (already parsed, so you do not need to call .json() on it):

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

The token field holds the fingerprint session token. If telemetry collection runs into problems, the object also includes an optional errors array you can log; with the npm package, sendClientTelemetry never rejects and resolves with { token: null, errors } instead. Extract result.token and send only that session token string to your backend.

Retrieve fingerprint data from your backend

Generate a backend-only Device Intelligence API key in the cside dashboard, store it as a server-side secret, and use it to exchange the session token with cside. The API key is shown only once and must not be exposed in browser code.

if (!token) {
  throw new Error("Fingerprint session token was not returned.");
}

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 }),
});

const data = await response.json();

Request body

Send JSON in the shape { "token": "<session token>" }, where the value is result.token from the previous step. Only the /token/v2/* endpoints take JSON; the /token/v1/* endpoints take the raw session token as a text/plain body.

Token lookup options

Use the authenticated endpoint for production server-to-server exchanges. The public endpoints are still available when an integration specifically needs the unauthenticated lookup flow.

EndpointAuthRequest bodyUse when
/token/v2/verifyAPI keyJSON { "token" }You need authenticated backend retrieval with a Device Intelligence API key.
/token/v2/device/entriesAPI keyJSON { "token", "limit", "offset" }You need completed evaluations for the same device ID as a session token.
/token/v1/clientNoneRaw token, text/plainYou need the public fingerprinting response payload.
/token/v1/clientIdNoneRaw token, text/plainYou only need the stable fingerprint ID from the public lookup flow.

Every endpoint returns 202 Accepted with { "status": "pending" } while the evaluation is still processing, then 200 OK once it has finished. /token/v2/device/entries accepts { "token": "...", "limit": 100, "offset": 0 } and returns { "device_id": "...", "entries": [...], "next_offset": 100, "has_more": true }, where each entry uses the /token/v2/verify response shape. limit is optional, defaults to 500, and is capped at 500. /token/v1/clientId returns JSON { "status": "completed", "device_id": "..." }. If no fingerprint ID exists for the session token, it returns 404.

Errors and rate limits

Error responses carry a JSON body { "error": "<message>", "code": <HTTP status> }:

StatusMeaning
400Malformed body, or a session token that cannot be decoded.
401Missing or invalid API key, an expired token, or a token issued for a different team than the API key.
404No evaluation exists for the token (token not found), or, on /token/v1/clientId, no fingerprint ID (fingerprint id not found).
429Rate limit exceeded. The response carries a retry-after: 60 header. The default limit is 1,000 requests per minute per team.

A completed evaluation that failed is not an HTTP error: it returns 200 OK with "status": "failed" and an error_message field. Check status before reading enrichment fields.

Example backend request

const csideResponse = 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 }),
});

const data = await csideResponse.json();

Interpret the backend response

Your backend returns a JSON response containing the identification result and associated signals. See the full response reference below.

/token/v2/verify response

The authenticated endpoint returns a flat JSON payload. Each entry in /token/v2/device/entries uses the same shape.

FieldTypeDescription
statusstringpending, completed, or failed. Enrichment fields are only present when completed.
error_messagestringWhy the evaluation failed. Only present when status is failed.
created_atnumberUnix timestamp (milliseconds) when the evaluation was created.
updated_atnumberUnix timestamp (milliseconds) of the last update.
fingerprintobjectThe full device fingerprint: browser, hardware, rendering, environment, and automation signals, plus the external_ids you attached. Same fields as the S3 export fingerprint object.
network_client_metricsobjectTCP and TLS characteristics of the connection.
geo_dataobjectCity-level geolocation for the visitor IP.
ip_enrichmentobjectASN, geo, and privacy data (hosting, proxy, relay, tor, vpn, datacenter signals) for the visitor IP.
vpn_evaluationobjectis_vpn, vpn_probability, vpn_confidence, reasons, features, and context.
botobjectThe bot verdict: is_bot, bot_probability, bot_confidence, bot_agent_id, reasons, per-model scores and thresholds, and session_event_count. See AI agent detection.
fingerprint_enrichmentobjectSignals derived from the fingerprint. throwaway_email_detected is true when the email external ID uses a disposable domain.
rule_evaluationsobjectLegacy rule outcomes (rules, policy_version). Empty on evaluations created after 2026-08-27; kept on the wire so older records still decode.
rule_evaluations_v2objectOutcomes from the rule engine: a values array where each item wraps one evaluation with rule_slug, rule_version, outcome, evaluated_as (live or shadow), transition, and evaluated_at.

The nested objects match the S3 export record structure, where each field is documented in detail.

rule_evaluations_v2

The table above summarizes this field. It carries the outcome of every rule that ran for the domain, nested three levels deep at rule_evaluations_v2.values[].evaluation, and the values are integers rather than strings, so they are worth spelling out. It is not part of the /token/v1/client payload documented below.

FieldTypeDescription
rule_slugstringThe rule’s identifier, for example "account_shared_detected".
outcomenumberThe evaluation result. See the table below.
evaluated_asnumberThe rule’s lifecycle state at evaluation time. See the table below.
transitionnumberWhether the outcome changed on this evaluation, independent of what the outcome is. 2 means unchanged since the last evaluation, 3 means it changed on this one. Combined with outcome: 1, a 3 means the rule just started matching; combined with outcome: 2, it means the rule just stopped matching. 1 is never produced.
key.key_hashstringA hash of the value the rule keyed on. This is a JSON string, not a number, so it survives JSON.parse without precision loss. It does not carry the account id itself.
event_time, evaluated_atobject{ seconds, nanos }, unlike the response’s top-level created_at and updated_at, which are millisecond timestamps.

outcome and evaluated_as are raw integers, not strings:

outcomeMeaningWhen you’ll observe it
0UnspecifiedNot produced
1TrueContinuously, on every evaluation while the condition holds
2FalseContinuously, on every evaluation while it doesn’t. This is the steady state for a healthy, non-matching rule, not a rare transition
3Missing (structural)Continuously, on every evaluation where the rule can’t read the field it needs, such as a wrong external-id key. Persistent, not a one-time blip
4Pending (transient)When a required measurement exists but is not yet warm. Distinct from a top-level status of pending: re-requesting the same token will not resolve it, but a later evaluation of the same entity may return 1 or 2
evaluated_asMeaning
1Draft
2Shadow (an experiment; can appear at any time)
3Active
4Archived

Every evaluated, published rule reports an entry on every event, at any outcome. rule_evaluations_v2 is a record of what was evaluated, not a notification stream, so outcome: 2 (false) and outcome: 3 (missing) are not suppressed: they ride in the array just as reliably as outcome: 1. transition is what tells you whether that outcome changed since the previous evaluation.

An absent entry for a given rule_slug means the rule wasn’t evaluated at all: it isn’t activated for the domain, it’s a measurement-only rule that doesn’t publish, or evaluation didn’t run for that event. It does not mean “evaluated to false” - that case is outcome: 2, present in the array like everything else. See Detect account sharing for a worked example that reads this field end to end.

Rule explanations

Published rules that evaluate true may include rule_evaluations_v2.values[].evaluation.explanation.nodes. Each visited node can describe its node_id, outcome, typed observed value, subject, and compared_to. Explanations are optional; false, pending, and missing outcomes do not include them. A missing explanation does not change the recorded outcome.

Treat subject and compared_to as display text, not a format to parse. Use node_id, typed observed values, and outcome for programmatic handling. Read these fields from rule_evaluations_v2, not the legacy rule_evaluations object. See the Events API for outcome values and the legacy distinction.

Top-level fields

FieldTypeDescription
linked_idstringThe browser session ID, or the event ID when no session ID was captured. It is not an identifier you supplied; your externalIds appear under tags as external_id:<key>.
tagsobjectA cside diagnostics map, not customer metadata: domain_id, team_id, request_id, session_id, fingerprint_match, network_match, detection support flags such as incognito_support, and your externalIds as external_id:<key> entries.
timestampnumberUnix timestamp (milliseconds) of the event.
event_idstringUnique identifier for this fingerprinting event.
urlstringThe page URL where the fingerprint was collected.
ip_addressstringThe visitor’s IP address.
user_agentstringThe raw User-Agent string from the visitor’s browser.
client_referrerstringAlways "unknown".
statusstringcompleted or failed.
error_messagestringPresent when status is failed.
domain_id, team_id, session_id, request_idstringEvaluation metadata copied from the internal record.
created_atstringEvaluation time as an RFC 3339 string (unlike /token/v2/verify, which uses milliseconds).

The response also carries network_client_metrics, vpn_evaluation, proxy_details, tampering_details, tampering_anomaly_score, velocity, and sdk objects. velocity counts are derived from the current event alone (its events.24_hours window is the only historical value); sdk reports platform: "js" with version: "unknown". These are shown in the full example but are not part of the stable contract.

browser_details

Details about the visitor’s browser and operating system.

FieldTypeDescription
browser_namestringBrowser name (e.g. "Chrome").
browser_major_versionstringMajor version number.
browser_full_versionstringFull version string.
osstringOperating system name.
os_versionstringOperating system version.
devicestringDevice type (e.g. "Other", "Mobile").

identification

The primary identification result for the visitor.

FieldTypeDescription
visitor_idstringThe stable device ID for this visitor. This is the only computed field in the object.
confidence.scorenumberAlways 0.0.
confidence.versionstringAlways "unknown".
visitor_foundbooleanAlways false.
first_seen_atnumberAlways equals timestamp.
last_seen_atnumberAlways equals timestamp.

supplementary_id_high_recall

A secondary identifier derived from your externalIds (preferring userId, then accountId) so one account can be linked across devices. It falls back to the device ID when no external ID was attached.

FieldTypeDescription
visitor_idstringHash of the chosen external ID, or the device ID when none was attached.
visitor_foundbooleantrue when an external ID was attached, otherwise false.
confidence.scorenumberAlways 0.0.
confidence.versionstringAlways "unknown".
first_seen_atnumberAlways equals timestamp.
last_seen_atnumberAlways equals timestamp.

proximity

Not populated. Always { "id": "unknown", "precision_radius": 0, "confidence": 0.0 }. Use geo_data and ip_enrichment on /token/v2/verify for location.

ip_info

IP address information for the visitor.

FieldTypeDescription
v4.addressstringIPv4 address.
v6.addressstringIPv6 address.

ip_blocklist

Whether the visitor’s IP appears on known blocklists.

FieldTypeDescription
email_spambooleanNot populated; always false.
attack_sourcebooleanNot populated; always false.
tor_nodebooleanIP is a Tor exit node.

Fraud & environment signals

Signals indicating suspicious or notable visitor attributes.

Some of these are not currently populated

root_apps, emulator, jailbroken, frida, mitm_attack, replayed, high_activity_device, cloned_app, privacy_settings, and location_spoofing are present in the response but are never set. They always return false (factory_reset_timestamp is always 0). Do not build detection or fraud logic on them. Each is marked in the table below.

FieldTypeDescription
botstringA plain string derived only from the User-Agent: "detected" when it contains bot, crawler, spider, or headless; "unknown" when no User-Agent was captured; otherwise "not_detected". It is not the bot model. Use bot.is_bot on /token/v2/verify for the real verdict. See AI agent detection.
root_appsbooleanDevice has root/superuser apps installed. Not currently populated; always false.
emulatorbooleanDevice is an emulator. Not currently populated; always false.
proxybooleanVisitor is using a proxy, relay, or datacenter IP.
proxy_confidencestring"low", "medium", or "high" when proxy is true; "unknown" otherwise.
vpnbooleanVisitor is using a VPN.
vpn_confidencestring"low", "medium", or "high" when the VPN model ran; "unknown" otherwise.
vpn_origin_timezonestringIANA timezone reported by the browser.
vpn_origin_countrystringCountry inferred from IP enrichment, or "unknown" when unavailable.
vpn_methodsobjectMethod-level signals related to VPN and anonymized traffic. See the subsection below.
incognitobooleanBrowser is in incognito/private mode.
tamperingbooleanBrowser attributes have been tampered with.
jailbrokenbooleanDevice is jailbroken. Not currently populated; always false.
fridabooleanFrida instrumentation toolkit detected. Not currently populated; always false.
virtual_machinebooleanVisitor is running in a virtual machine.
developer_toolsbooleanBrowser developer tools are open.
mitm_attackbooleanMan-in-the-middle attack detected. Not currently populated; always false.
replayedbooleanThe request is a replay of a previous request. Not currently populated; always false.
high_activity_devicebooleanUnusually high number of identifications from this device. Not currently populated; always false.

The disposable email signal is not part of this response. Read fingerprint_enrichment.throwaway_email_detected from /token/v2/verify instead.

vpn_methods

vpn_methods contains method-level signals related to VPN and anonymized traffic. Use the top-level vpn field as the overall verdict. A method can be true when vpn is false, such as when cside detects proxy or relay traffic that the VPN model does not classify as a VPN.

FieldTypeDescription
timezone_mismatchbooleanThe browser timezone does not match the IP geolocation.
public_vpnbooleanThe IP address matches a known public VPN or data center signal.
auxiliary_mobilebooleanReserved for mobile-specific detection. This field is currently false for browser events.
os_mismatchbooleanThe operating system reported by the browser does not match the network signature.
relaybooleancside detected anonymizing relay, proxy, or relay-like traffic.
Flattened field names

Some data pipelines prefix and flatten nested fields. In those datasets, vpn_methods.relay can appear as cside_fingerprint_vpn_methods_relay.

raw_device_attributes

Low-level browser fingerprint components.

FieldTypeDescription
mathstringNot populated; always "unknown".
vendorstringGPU/graphics vendor string.

/token/v1/client response reference

This reference applies to the public /token/v1/client endpoint. It is an older, flatter view over the same evaluation. Many of its fields are fixed placeholders that were kept for backwards compatibility; the callout below lists them. For new integrations, prefer /token/v2/verify.

Always placeholder values on v1

These fields are present in every response but never computed:

  • identification.confidence and supplementary_id_high_recall.confidence are always { "score": 0.0, "version": "unknown" }
  • identification.visitor_found is always false; first_seen_at and last_seen_at equal the event timestamp
  • proximity is always { "id": "unknown", "precision_radius": 0, "confidence": 0.0 }
  • client_referrer is always "unknown"
  • ip_blocklist.email_spam and ip_blocklist.attack_source are always false (only tor_node is populated)
  • raw_device_attributes.math is always "unknown"

Do not build detection or fraud logic on them.

Full response example

A completed /token/v1/client response. Nested objects that are not part of the stable contract (network_client_metrics, vpn_evaluation, velocity) are abbreviated.

{
  "linked_id": "b3f1c9e2-4d7a-4b0e-9f1a-2c8d7e6f5a41",
  "tags": {
    "domain_id": "7371466449724252160",
    "team_id": "7196353823991660544",
    "request_id": "01J9ZK3Q8W",
    "session_id": "b3f1c9e2-4d7a-4b0e-9f1a-2c8d7e6f5a41",
    "fingerprint_match": "7371466449724252160",
    "external_id:userId": "1234567890",
    "incognito_support": "supported",
    "developer_tools_support": "supported",
    "virtual_machine_support": "heuristic",
    "tor_support": "ipinfo_privacy",
    "visit_summary_source": "recent_fingerprint.available"
  },
  "timestamp": 1708102555327,
  "event_id": "289076244130249728",
  "url": "http://www.example.com/login",
  "ip_address": "61.127.217.15",
  "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...",
  "client_referrer": "unknown",
  "browser_details": {
    "browser_name": "Chrome",
    "browser_major_version": "74",
    "browser_full_version": "74.0.3729",
    "os": "Windows",
    "os_version": "7",
    "device": "Other"
  },
  "identification": {
    "visitor_id": "Ibk1527CUFmcnjLwIs4A9",
    "confidence": { "score": 0.0, "version": "unknown" },
    "visitor_found": false,
    "first_seen_at": 1708102555327,
    "last_seen_at": 1708102555327
  },
  "supplementary_id_high_recall": {
    "visitor_id": "3HNey93AkBW6CRbxV6xP",
    "visitor_found": true,
    "confidence": { "score": 0.0, "version": "unknown" },
    "first_seen_at": 1708102555327,
    "last_seen_at": 1708102555327
  },
  "proximity": {
    "id": "unknown",
    "precision_radius": 0,
    "confidence": 0.0
  },
  "bot": "not_detected",
  "root_apps": false,
  "emulator": false,
  "ip_info": {
    "v4": { "address": "94.142.239.124" },
    "v6": { "address": "2001:db8:3333:4444:5555:6666:7777:8888" }
  },
  "ip_blocklist": {
    "email_spam": false,
    "attack_source": false,
    "tor_node": false
  },
  "proxy": true,
  "proxy_confidence": "low",
  "proxy_details": {
    "proxy_type": "datacenter",
    "last_seen_at": 1708102555327,
    "provider": "Example Hosting"
  },
  "vpn": false,
  "vpn_confidence": "high",
  "vpn_origin_timezone": "Europe/Amsterdam",
  "vpn_origin_country": "NL",
  "vpn_methods": {
    "timezone_mismatch": false,
    "public_vpn": true,
    "auxiliary_mobile": false,
    "os_mismatch": false,
    "relay": true
  },
  "vpn_evaluation": { "is_vpn": false, "vpn_probability": 0.04, "vpn_confidence": 0.91 },
  "incognito": false,
  "tampering": false,
  "tampering_anomaly_score": 0.0,
  "tampering_anti_detect_browser": false,
  "tampering_details": { "anomaly_score": 0.0, "anti_detect_browser": false },
  "cloned_app": false,
  "factory_reset_timestamp": 0,
  "jailbroken": false,
  "frida": false,
  "privacy_settings": false,
  "virtual_machine": false,
  "location_spoofing": false,
  "velocity": {
    "distinct_ip": { "5_minutes": 1, "1_hour": 1, "24_hours": 1 },
    "distinct_country": { "5_minutes": 0, "1_hour": 0, "24_hours": 0 },
    "events": { "5_minutes": 1, "1_hour": 1, "24_hours": 3 },
    "ip_events": { "5_minutes": 1, "1_hour": 1, "24_hours": 1 },
    "distinct_ip_by_linked_id": { "5_minutes": 1, "1_hour": 1, "24_hours": 1 },
    "distinct_visitor_id_by_linked_id": { "5_minutes": 1, "1_hour": 1, "24_hours": 1 }
  },
  "developer_tools": false,
  "mitm_attack": false,
  "sdk": { "platform": "js", "version": "unknown", "integrations": [] },
  "replayed": false,
  "high_activity_device": false,
  "raw_device_attributes": {
    "math": "unknown",
    "vendor": "Google Inc."
  },
  "network_client_metrics": { "client_ip": "61.127.217.15", "tls_ja4": "t13d1516h2_8daaf6152771_b186095e22b6" },
  "status": "completed",
  "domain_id": "7371466449724252160",
  "team_id": "7196353823991660544",
  "session_id": "b3f1c9e2-4d7a-4b0e-9f1a-2c8d7e6f5a41",
  "request_id": "01J9ZK3Q8W",
  "created_at": "2024-02-16T17:35:55.327Z"
}
Was this page helpful?