All articles Fundamentals

requestId vs visitorId vs linkedId: The Three IDs of Device Data

Every device intelligence integration involves three identifiers, and almost every integration bug involves confusing two of them. They look similar, they arrive together, and they have very different jobs. Once the difference is clear, where to store each one and what to send to your server follows naturally.

In one line each:

  • requestId is one identification event: this page load, this signup attempt.
  • visitorId is the device: the stable identifier for the browser or app.
  • linkedId is your user: the account id from your own system.

requestId: a single event

Each call to identify() produces one event and returns its requestId:

const prynt = await Prynt.load({ apiKey: 'pk_live_…' });
const { requestId, visitorId, confidence } = await prynt.identify({ tag: { action: 'signup' } });

The event behind that requestId holds everything known at that moment: the IP and its country, the page URL, Smart Signals such as VPN or bot, the decision and risk score, and the tag you passed. Think of it as a receipt for one observation.

The requestId is what your browser sends to your server. Your server then fetches the event with the secret key:

// GET https://api.pryntid.com/v1/events/{requestId}
// Authorization: Bearer sk_live_…
const event = await prynt.getEvent(requestId);   // @prynt/node

The response is the source of truth: visitorId, decision, riskScore, smartSignals, ip, ipCountry, url, linkedId, createdAt, the visitor record and accountsOnDevice.

Two properties matter. A requestId is not secret and is harmless to expose, because reading the event requires your secret key. And a requestId does not expire and is not single-use, so if replay matters, as it does at signup, guard against it yourself: reject an event older than a few minutes, or one that already carries a linkedId.

Store it on records where you may need to look at the evidence later: the signup, the order, the payout request. One column, and support can reopen the exact event when someone disputes a decision.

visitorId: the device

The visitorId stays the same across many events from the same browser or app, including after cookies are cleared and in private browsing. It is what lets you say “this is the same device that signed up last week.”

It comes with a confidence, a score from 0 to 1 and a level of low, medium or high. Identification is probabilistic: most of the time the same device gets the same visitorId, but a major browser update, a hardware change or heavy anti-fingerprinting can occasionally produce a new one, and very similar devices can rarely collide. Confidence tells you how much weight to put on a given match.

Use it for device history and limits: how many accounts this device holds, how often it has been seen, whether it has been flagged before. Prynt does much of this for you through accountsOnDevice and the risk signals, so you often do not need to build your own device tables.

Store it next to the requestId on important records, taken from the server-side event, never from the browser.

Do not treat it as a person. A family laptop is one visitorId and three people. One person with a phone and a laptop is two visitorIds.

linkedId: your user

linkedId is your own identifier, typically the user id in your database, attached to an event. It is how Prynt learns which accounts live on which devices.

You can attach it two ways. When the user is already known, say on a logged-in page, pass it to identify():

await prynt.identify({ tag: { action: 'withdrawal' }, linkedId: currentUser.id });

When the user is created after identification, which is the signup case, attach it from your server once the account exists:

await prynt.updateEvent(requestId, { linkedId: String(newUser.id) });
// PUT /v1/events/{requestId}  { "linkedId": "user_123" }

From then on, any event from that device includes the account in accountsOnDevice:

"accountsOnDevice": {
  "count": 2,
  "truncated": false,
  "accounts": [
    { "linkedId": "user_456", "events": 3, "firstSeenAt": "…", "lastSeenAt": "…" },
    { "linkedId": "user_123", "events": 7, "firstSeenAt": "…", "lastSeenAt": "…" }
  ]
}

Use an opaque id, not an email address or name. A linkedId is a string of up to 190 characters, and an internal id keeps personal data out of the device record while still letting you join back to your own tables.

How they fit together at signup

The three IDs appear in a fixed order in the standard signup recipe:

  1. Browser: identify() returns a requestId. Send it with the form.
  2. Server: fetch the event by requestId. Read visitorId, decision and accountsOnDevice. Refuse or flag if the device already holds too many accounts.
  3. Server: create the user, then attach its id as linkedId to the same requestId.

The Node server-side verification post shows this end to end, including error handling.

Common mistakes

Trusting the browser’s visitorId. The browser result includes visitorId for convenience. Anyone can change a form field. Your server must read visitorId from the event it fetched, never from the request body.

Using requestId as a device identifier. A new requestId is created on every identification. Counting distinct requestIds per user tells you how many times they loaded a page, not how many devices they have.

Making visitorId a primary key. It is a property of a device at a point in time, with a confidence. Foreign keys, uniqueness constraints and logins belong on your own user ids.

Forgetting to link. If you never call updateEvent after signup, accountsOnDevice stays empty and every device looks new. This is the most common reason a multi-accounting check “never fires.”

Linking before the account exists. Linking a pre-generated id and then failing to create the user leaves a phantom account on the device, which can wrongly count against a legitimate retry.

Putting personal data in linkedId or tags. Use ids, not emails, names or health and financial details.

Mixing environments. A secret key only sees its own environment. A requestId from a test key looked up with a live key returns not found.

Quick reference

requestIdvisitorIdlinkedId
IdentifiesOne identification eventA browser or deviceAn account in your system
Created byEach identify() callPrynt, stable across eventsYou
Send from browser to serverYesNoNot needed; server knows the user
Authoritative sourceReturned by the agentThe server-side eventYour database
Store on your recordsYes, for evidenceYes, from the server eventIt is your user id

The glossary and the device intelligence terms post define the surrounding vocabulary, and what device fingerprinting is in 2026 explains how the visitorId is produced. To see all three IDs in a live result, run an identification on the playground.

Try it free

Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.

Keep reading