Skip to main content
End-to-end flow
Language

Retrieving datapoints

Retrieve cside Device Intelligence datapoints after /client returns a session token, then exchange it from your backend for fingerprint and device data.

The browser does not need to know what sendClientTelemetry(externalIds?) sends internally. Call the function, read the returned session token, and let your backend exchange that session token with https://api.cside.com/token/v2/verify to retrieve fingerprint, bot, browser, device, IP, and environment datapoints.

Development versus production

Local demo environments may proxy POST /token/v1/* for testing. In production, your deployed backend should call the authenticated POST /token/v2/verify endpoint directly.

End-to-end flow

  1. The browser calls sendClientTelemetry(externalIds?).
  2. sendClientTelemetry posts telemetry to https://edge.csidefd.com/client. Allow that host in your CSP connect-src.
  3. /client returns JSON in the form { "token": "..." }, where token is the fingerprint session token.
  4. Your backend sends that session token to https://api.cside.com/token/v2/verify with a server-side Device Intelligence API key.

Using sendClientTelemetry

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

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

const { token: sessionToken } = result;

if (!sessionToken) {
  throw new Error("No fingerprint session token returned.");
}

After you extract sessionToken, send it to your backend. Your backend performs the token exchange with cside.

You can also call sendClientTelemetry() with no arguments. Use externalIds only when you want to attach your own identifiers, such as userId, orderId, or email, to the fingerprint.

The example above uses the sendClientTelemetry that client.js exposes on window. If you installed the NPM package, take the same function from initDeviceIntelligence instead and get types with it:

let token = null;

try {
  const { initDeviceIntelligence } = await import("@cside.dev/device-intelligence");
  const { sendClientTelemetry } = await initDeviceIntelligence({
    teamID: "[your-team-id]",
  });
  const result = await sendClientTelemetry({ userId: "1234567890" });
  token = result.token;
  if (result.errors) console.warn(result.errors);
} catch (error) {
  console.warn("Device Intelligence unavailable", error);
}

Dynamic import lets your bundler put the collection code in a separate chunk. Start the import and initialization as early as practical: waiting until a checkout or login action delays collection and can miss earlier signals. Catch import failures as well as initialization errors, and keep the page working without a token.

Everything below applies either way. The function, its arguments, and the session token it returns are the same.

Choose your external identifiers

externalIds accepts any string keys, but two of them drive built-in detections:

KeyUsed byFormat
userIdThe Shared account detected and Multiple accounts on one device rules, and the Identity graphYour stable account identifier. cside stores only a one-way hash of it.
paymentCardIdThe Card testing on one device or account ruleA one-way hash of the card, or your processor’s card fingerprint or token, with at most the last four digits appended. Never the card number.

The Identity graph links a device to an account through userId, teamId, or customerId; without one of those keys, a session has no account to link to.

Filtering by external ID in the dashboard

The Events filter parses external ID keys and values as literal strings. For example, 00123, 9007199254740993, and true are not converted to numbers or booleans when a filter is entered or restored from the URL. This describes filter parsing, not how identifiers are stored: userId is still hashed as described above.

A common checkout fraud use case is detecting one device that attempts payment with many different cards. Attach paymentCardId at payment time:

const result = await sendClientTelemetry({
  userId: "1234567890",
  orderId: "order-456",
  // Never the card number: a one-way hash of it, plus the last 4 at most.
  paymentCardId: "a3f5c8d19b02e74f_4242",
});

The same card must always produce the same paymentCardId, so use a deterministic hash or your processor’s card token, never a per-attempt ID. Do not send a full card number, CVC, expiration date, or other raw payment card data through externalIds.

Retrieval options

MethodStatusBest for
APIAvailableRequest-time decisions and backend enrichment
S3 exportAvailable when enabledBatch analysis, warehousing, and offline review
WebhookNot availablePush delivery into internal systems
WebSocketNot availableLive streams and near-real-time dashboards

Generate a backend API key

Generate a Device Intelligence API key under Team Settings > Device Intelligence in the cside dashboard before calling the authenticated endpoint. The key starts with cside_tgatv1_, is shown only once, and must be stored in your backend secrets manager.

The API key is a team-scoped backend credential. Do not send it to the browser, expose it in frontend bundles, or write it into HTML from a Worker.

API retrieval with the session token

The session token does not contain the full fingerprint payload. Use it as a lookup token from your backend with the authenticated cside endpoint.

Use /token/v2/verify when you need the full JSON payload from a server-to-server integration.

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"'"}'

The endpoint returns 202 Accepted with { "status": "pending" } while the evaluation is still processing. It returns 200 OK once the evaluation has finished, with "status": "completed" and the payload, or "status": "failed" and an error_message.

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

const datapoints = await csideResponse.json();

if (!csideResponse.ok) {
  throw new Error(datapoints.error || "Fingerprint lookup failed.");
}

if (datapoints.status === "failed") {
  throw new Error(datapoints.error_message || "Fingerprint evaluation failed.");
}

The completed response includes status, created_at, updated_at, and any available enrichment fields: fingerprint, network_client_metrics, geo_data, ip_enrichment, vpn_evaluation, bot, fingerprint_enrichment, rule_evaluations_v2, and the legacy rule_evaluations. See the /token/v2/verify response reference for each field.

Errors and rate limits

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

StatusMeaning
400Malformed body, or a session token that cannot be decoded.
401Missing or invalid API key, an expired token, or a token issued for another team.
404No evaluation exists for the token.
429Rate limit exceeded. The response carries a retry-after: 60 header.

The default rate limit is 1,000 requests per minute per team, with short bursts allowed. Back off for the retry-after period before retrying.

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.

Retrieve previous evaluations for the same device

Use /token/v2/device/entries when you need completed evaluations associated with the same device ID as the session token. This is useful for account risk scoring, fraud review queues, abuse investigation, and linking a new session to recent activity from the same browser fingerprint.

The endpoint uses the same backend-only Device Intelligence API key as /token/v2/verify. It returns results newest first and supports pagination with limit and offset.

curl https://api.cside.com/token/v2/device/entries \
  --request POST \
  --header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "token": "'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'",
    "limit": 100,
    "offset": 0
  }'

Request body

FieldTypeRequiredDescription
tokenstringYesSession token returned by sendClientTelemetry.
limitnumberNoMaximum number of entries to return. Defaults to 500 and is capped at 500.
offsetnumberNoNumber of entries to skip from the newest result. Defaults to 0. Use the previous response’s next_offset to request the next page.

Response body

The response contains the resolved device_id, an entries array, and pagination metadata. Each entry uses the same flat payload shape as /token/v2/verify.

{
  "device_id": "a1b2c3d4e5f6...",
  "entries": [
    {
      "status": "completed",
      "created_at": 1779441600000,
      "updated_at": 1779441600000,
      "fingerprint": {},
      "network_client_metrics": {}
    }
  ],
  "limit": 100,
  "offset": 0,
  "next_offset": 100,
  "has_more": true
}
FieldTypeDescription
device_idstringDevice ID resolved from the session token.
entriesarrayCompleted evaluations for that device ID, ordered newest first.
limitnumberEffective page size after server-side clamping.
offsetnumberOffset used for this response.
next_offsetnumber | nullOffset to send on the next request. null means there are no more entries.
has_morebooleantrue when another page is available.

Backend example

Use next_offset from the response rather than calculating offsets yourself. Stop when has_more is false.

async function fetchDeviceEntries(sessionToken) {
  const entries = [];
  let offset = 0;
  const limit = 100;

  while (offset !== null) {
    const response = await fetch("https://api.cside.com/token/v2/device/entries", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.CSIDE_FINGERPRINT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        token: sessionToken,
        limit,
        offset,
      }),
    });

    const page = await response.json();

    if (!response.ok) {
      throw new Error(page.error || "Device entries lookup failed.");
    }

    entries.push(...page.entries);
    offset = page.has_more ? page.next_offset : null;
  }

  return entries;
}

If you only need the first page, omit limit and offset. The API returns up to 500 completed evaluations by default.

Public token endpoints

/token/v1/client and /token/v1/clientId remain available for integrations that specifically need the unauthenticated public lookup flow.

Use /token/v1/client when you need the public JSON payload without API-key authentication.

curl https://api.cside.com/token/v1/client \
  --request POST \
  --header "Content-Type: text/plain" \
  --data "$CSIDE_FINGERPRINT_SESSION_TOKEN"

Use /token/v1/clientId when you only need the stable fingerprint ID.

curl https://api.cside.com/token/v1/clientId \
  --request POST \
  --header "Content-Type: text/plain" \
  --data "$CSIDE_FINGERPRINT_SESSION_TOKEN"

For /token/v1/*, send the raw session token as a text/plain body and do not wrap it in JSON. Only the /token/v2/* endpoints take { "token": "..." }. Both v1 endpoints return 202 Accepted with { "status": "pending" } while the evaluation is processing. /token/v1/clientId then returns JSON:

{ "status": "completed", "device_id": "a1b2c3d4e5f6..." }

If no fingerprint ID exists for the session token, it returns 404 with { "error": "fingerprint id not found", "code": 404 }. The v1 endpoints share the same error shape and rate limit as /token/v2/verify, keyed on the team in the token.

S3 export

cside can export fingerprint records to S3 when fingerprint S3 export is configured for a domain. Use this for batch workflows where you do not need a request-time decision.

Each exported record is the complete evaluation, not a summary: the identity fields, the full device fingerprint, and the network, IP, geo, VPN, bot, rule, and behavioral signals cside computed for that visit. See the S3 export guide for the setup steps and the full record structure.

Webhooks and WebSocket streams

Neither is available. S3 is the only export destination for Device Intelligence data, and there is no account-level switch that enables webhook or WebSocket delivery.

If you need data outside a batch window, call the API at request time.

Choose the simplest retrieval path

Use the API for request-time decisions and the S3 export for batch delivery into your own storage and pipelines.

Was this page helpful?