PintellyPintelly

User Identification

Tell Pintelly which of your users is browsing, so their sessions, events and session replays are recorded under your own user ID. Call Pintelly.identify() after login and Pintelly.reset() on logout.

Quick reference

CallWhenEffect
Pintelly.identify(userId)After login, and on every page load while logged inRecords the visitor under userId and starts full tracking for them.
Pintelly.reset()On logoutEnds the identity and starts a new session recording.
Pintelly.setConsent(false)When you withdraw tracking for this visitorEnds the identity, stops tracking and discards unsent events.
Pintelly.getUserId()DebuggingReturns the current user ID, or null while tracking is off.

identify() is your decision, as the site owner and data controller, to track that person under your identifier. Pintelly treats the call as your consent declaration:

  • Full tracking starts for that visitor, including session replay and every other feature enabled in Project Settings, even if the visitor has not answered a consent prompt.
  • A refusal from the visitor's side does not stop it. This covers Pintelly's own consent prompt and your consent tool (Cookiebot, OneTrust and so on). Pintelly's prompt closes and is not shown to an identified visitor.
  • Only you can end it, with reset() or setConsent(false).
  • Records collected this way are marked as identify-based. The dashboard's Consented only filter shows only visitors who consented themselves, so it leaves these records out.

Before calling identify(), make sure you have a legal basis for tracking that person and have informed them, for example in your privacy notice or terms. Pintelly records and validates the data you send. It does not decide on your behalf whether you may send it.

identify() does not turn on features that are off in Project Settings or not included in your plan.

The user ID

  • A string of 1 to 256 characters, with no control characters such as newlines or tabs.
  • Stable: the same person gets the same ID on every device and visit. Use your database's user ID.
  • Opaque: prefer an internal ID such as usr_8f3a2c or a UUID over an email address or name. The ID is visible to everyone on your Pintelly team and is stored with every event.
  • Case-sensitive: User_1 and user_1 are two different people.

An invalid ID is not sent. The browser console shows [Pintelly] Invalid userId…, and Pintelly's servers reject it too.

Installation

The Pintelly script loads asynchronously, so window.Pintelly may not exist yet when your code runs. Calls made before the SDK finishes starting up are kept and applied once it is ready. You only need to wait for window.Pintelly itself:

// Runs fn once the Pintelly script has loaded (checks for up to 10 seconds).
function withPintelly(fn, tries = 100) {
  if (window.Pintelly) fn(window.Pintelly);
  else if (tries > 0) setTimeout(() => withPintelly(fn, tries - 1), 100);
  else console.warn('Pintelly did not load; check that its script tag is on this page.');
}

Plain JavaScript

// After a successful login, and on every page load while logged in:
withPintelly((p) => p.identify(currentUser.id));

// On logout, before redirecting:
withPintelly((p) => p.reset());

React

Each example uses the withPintelly helper above. Put it in a shared module and import it.

import { useEffect } from 'react';

export function PintellyIdentity({ user }) {
  useEffect(() => {
    if (user) withPintelly((p) => p.identify(String(user.id)));
  }, [user?.id]);
  return null;
}

// In your logout handler:
async function logout() {
  withPintelly((p) => p.reset());
  await api.logout();
}

Next.js (App Router)

'use client';
import { useEffect } from 'react';

declare global {
  interface Window { Pintelly?: { identify(id: string): void; reset(): void } }
}

export function PintellyIdentity({ userId }: { userId: string | null }) {
  useEffect(() => {
    if (userId) withPintelly((p) => p.identify(userId));
  }, [userId]);
  return null;
}

Render <PintellyIdentity userId={session?.user.id ?? null} /> in your root layout. Call withPintelly((p) => p.reset()) in the client code that signs the user out.

Vue

import { watch } from 'vue';

watch(() => auth.user?.id, (id) => {
  if (id) withPintelly((p) => p.identify(String(id)));
}, { immediate: true });

Behavior details

  • Persistence: the identity is kept in a first-party cookie on your root domain for up to two years, so it carries across pages and subdomains. Calling identify() on every page load is still recommended; it records one UserIdentified event per session, and repeat calls in the same session send nothing.
  • Switching users: calling identify() with a different ID ends the previous identity first. The new person gets a new session and a new replay, so one person's activity never appears under another.
  • Logout: reset() sends events already queued under the person they belong to, then ends the identity. If the visitor had consented themselves, tracking continues as a new anonymous visitor. Otherwise only anonymous measurement continues.
  • Withdrawal: setConsent(false) ends the identity and discards events not yet sent. Data already received is not deleted.
  • History: identification applies from the call onward. It does not rewrite events that were already sent.
  • Bots: traffic from known bot user agents is labeled as bot traffic and shown in the separate bot view, identified or not.

Find a user in the dashboard

Open Visitors in your project and select the visitor whose ID matches your user ID. The profile lists every session of that person, newest first, with entry and exit page, duration, page count, rage clicks, errors, anomalies and feedback. Watch replay opens the recording with a clickable timeline of what they did. A session that started before login is included in full, so you can see what happened before they signed in.

Show a user's replays in your own admin panel

You can embed one user's session list and replays in your own admin panel, for example on that user's profile page. The page is served by Pintelly inside an iframe. Replay content never runs on your domain, and your Pintelly login is not needed.

  1. In Settings → Security → API tokens, create a token of type Replay embed. Keep it on your server only.
  2. When an admin opens a user's profile, your server asks Pintelly for a short-lived embed token for that one user:
// Node.js (server side)
const res = await fetch('https://pintelly.com/api/embed/sessions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.PINTELLY_EMBED_TOKEN}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ domainId: 'YOUR_PROJECT_ID', userId: user.id }),
});
const { url, expiresAt } = await res.json();
// PHP (server side)
$ch = curl_init('https://pintelly.com/api/embed/sessions');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('PINTELLY_EMBED_TOKEN'), 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode(['domainId' => 'YOUR_PROJECT_ID', 'userId' => (string) $user->id]),
]);
$embed = json_decode(curl_exec($ch), true);
  1. Put the returned url in an iframe. Before the #, add &lang=tr for Turkish and &theme=dark for the dark theme (light is the default).
<iframe src="EMBED_URL" style="width:100%;height:900px;border:0" title="Session replays"></iframe>
  1. While the page is open and visible, the iframe renews its token every few minutes, so nobody is logged out while browsing or watching. A closed or hidden page stops renewing and its token lapses within 10 minutes. However often it is renewed, a token stops working 8 hours after it was minted. When that happens, a replay that is already playing keeps playing, a notice appears, and the iframe sends { type: 'pintelly:embed-expired' } with postMessage. Mint a new URL and reload the iframe:
window.addEventListener('message', (e) => {
  if (e.origin === 'https://pintelly.com' && e.data?.type === 'pintelly:embed-expired') reloadEmbed();
});

Security model:

  • The embed token opens one user's sessions and replays in one project, read-only. Every recording is checked against that user on Pintelly's servers.
  • Only a hash of the embed token is stored. Revoking the API token, removing the token owner's access to the project or a plan without session replay ends embedding immediately, not at expiry.
  • The page can only be framed by your project's verified domain and its subdomains, for example https://example.com and https://admin.example.com. An admin panel on a different domain cannot frame it.
  • The token is passed in the URL fragment, which browsers never send to servers or in a Referer, and the page removes it from the address bar on load.
  • Each token mint, list and playback is recorded for 90 days. The token's owner sees this log under Settings → Security → API tokens → Access log.
  • Never put the API token in browser code. Mint embed tokens only after your own admin authorization check.

Delete a user's data

To honor a deletion request (KVKK article 11, GDPR article 17), open the user in Visitors and choose Delete this person's data. You can also do it from your server with an API token of type Visitor data deletion:

const res = await fetch('https://pintelly.com/api/analytics/visitor-deletions/YOUR_PROJECT_ID', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.PINTELLY_DELETE_TOKEN}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: user.id }),
});
const request = await res.json(); // 202 { id, status: 'pending' }
// Poll until status is 'done' or 'failed':
// GET https://pintelly.com/api/analytics/visitor-deletions/YOUR_PROJECT_ID/{request.id}

Deletion removes the user's events and all events of their sessions (including the part before identify()), their session replays, feedback with its screenshots, and anomaly and AI analysis records in that project. It runs in the background and usually finishes within minutes. Replay storage shared with identical content from other recordings is removed by the next retention run.

Every request and its result is listed in the project's Settings → Data deletions. Only the project owner and members whose role has write access to Analytics can delete a person's data.

Data that arrives after the request is not deleted. Call Pintelly.reset() or stop calling identify() for that user first. Aggregated counts in charts are not per-person and are kept.

Verify your integration

  1. Log in on your site, open the browser console and run Pintelly.getUserId(). It should return your user ID.
  2. In Visitors, find that ID. Your test session should appear within a minute.
  3. Log out and run Pintelly.getUserId() again. It should return a different ID, or null if you had not consented as a visitor.
  4. Log in as a second user in the same browser and confirm the two users have separate sessions in Visitors.
  5. If you use a consent prompt, refuse it before logging in and confirm the identified session is still recorded. This is expected (see "Consent" above).

Troubleshooting

SymptomCheck
Pintelly.getUserId() returns null after loginidentify() was not called, or it received an invalid ID. Look for [Pintelly] warnings in the console.
window.Pintelly is undefinedThe script tag is missing on this page, blocked by a content blocker or CSP, or your code ran before it loaded. Use withPintelly.
Visitor appears but has no replaySession Replay is off in Project Settings, not in your plan, or the recording is past its retention period.
Two people share one profilereset() is not called on logout, or both accounts send the same ID.
Identified visitors missing from chartsThe Consented only filter is on. It leaves out identify-based records by design.

Instructions for AI coding assistants

Copy this block into your coding assistant together with this page's URL.

Integrate Pintelly user identification into this codebase.

Context:
- The Pintelly script is already installed and exposes window.Pintelly once loaded.
- Pintelly.identify(userId) records the visitor under userId and starts full tracking,
  including session replay. It is the site owner's consent declaration and overrides a
  visitor-side refusal. Only Pintelly.reset() or Pintelly.setConsent(false) ends it.
- Calls made before the SDK is ready are kept; only window.Pintelly must exist.

Do:
1. Add this helper once, in a shared client-side module:
   function withPintelly(fn, tries = 100) {
     if (window.Pintelly) fn(window.Pintelly);
     else if (tries > 0) setTimeout(() => withPintelly(fn, tries - 1), 100);
     else console.warn('Pintelly did not load; check that its script tag is on this page.');
   }
2. Call withPintelly((p) => p.identify(String(user.id))) wherever the authenticated user
   becomes known: after login, after signup, and on app start when a session is restored.
3. Call withPintelly((p) => p.reset()) in every logout path, before redirecting.
4. Use the application's stable internal user ID (database primary key or UUID).

Do not:
- Do not pass an email address, name, phone number or other personal data as the ID.
- Do not pass IDs longer than 256 characters or containing control characters.
- Do not call identify() for anonymous or guest users.
- Do not call identify() on the server; it runs in the browser only.
- Do not add Pintelly.setConsent(true) next to identify(); identify already starts tracking.
- Do not remove or change the existing consent banner integration.

Optional, only if asked to embed replays in the admin panel:
5. Add a server-side endpoint, callable only by authorized admins, that POSTs
   { domainId, userId } to https://pintelly.com/api/embed/sessions with
   "Authorization: Bearer <PINTELLY_EMBED_TOKEN from server env>" and returns { url }.
6. Render that url in an <iframe> on the user's admin profile page. On a window
   "message" event from origin https://pintelly.com with data.type ===
   'pintelly:embed-expired', fetch a new url and replace the iframe src.
7. Never expose the API token to the browser; never mint without the admin check.

Afterwards, list every file you changed and every login, signup, session-restore and
logout path you found, and say which of them now call identify() or reset().