User Identification
Track and identify authenticated users across AI agent sessions
Overview
Important: This feature is for your customer-facing website, not the Checkpoint dashboard. The userId comes from your existing analytics (Amplitude, GA4, etc.), not from Checkpoint.
The Checkpoint User Identification feature allows you to associate authenticated users on your website with their AI agent sessions. This is particularly valuable for tracking when users interact with your site through ChatGPT's agent mode, Perplexity, or other AI assistants while logged into their accounts on your website.
This feature is exposed on window.Checkpoint. The previous global window.AgentShield and
the agentshield:* events remain as deprecated aliases (both resolve to the same object /
both fire). The pixel writes two device-session cookies with identical contents —
checkpoint_user (canonical) and agentshield_user (legacy) — and reads whichever it finds.
Server-Authoritative Attribution: UTM and marketing attribution tracking is handled
server-side via secure cookies. When users visit your site with UTM parameters (e.g.,
?utm_source=google&utm_campaign=spring), they're captured in middleware and enriched into all
analytics events — no client-side code needed.
Prerequisites
- The Checkpoint Pixel installed on your site —
window.Checkpointonly exists oncepixel.jshas loaded. - Your Project ID — see Credentials.
- A
userIdfor the signed-in user, from your own auth or analytics stack.
Why User Identification?
Use Cases
- Unified Analytics: Sync user data between Checkpoint, Google Analytics, Amplitude, and other analytics platforms
- Personalized AI Responses: Track authenticated users' AI agent interactions
- Session Attribution: Connect unattributed AI sessions to known users when they log in
- Cross-Platform Tracking: Maintain user identity across different AI platforms and sessions
Benefits
- Track the complete user journey from AI agent discovery to conversion
- Understand how authenticated users interact with AI agents
- Segment AI traffic by user properties (plan, company, role, etc.)
- Build accurate conversion funnels that include AI touchpoints
How It Works
Here's the typical workflow:
- User visits your website with both the Checkpoint pixel and your analytics (Amplitude, GA4, etc.)
- User logs in to your website → Your analytics identifies them as
userId: "abc123"with email"visitor@gmail.com" - You sync the identification → Call
Checkpoint.identify("abc123", { email: "visitor@gmail.com" }) - AI agent visits your website → Checkpoint tracks it with the same
userId: "abc123" - You see in Checkpoint dashboard → The detection shows
userId: "abc123"andemail: "visitor@gmail.com"
Expected Result: The same user shows up with the same ID in both your analytics and Checkpoint, allowing you to correlate AI traffic with your existing user data.
Implementation
Basic Usage
For traditional websites, WordPress, Shopify, and non-framework sites:
// When user logs in
function handleUserLogin(user) {
var attempts = 0;
var maxAttempts = 50; // Try for up to 5 seconds
// Wait for Checkpoint to load before identifying
function identifyUser() {
if (window.Checkpoint) {
window.Checkpoint.identify(user.id, {
email: user.email,
name: user.name,
plan: user.subscription_plan,
company: user.company_name
});
console.log('User identified:', user.id);
} else if (attempts < maxAttempts) {
// Pixel not loaded yet, retry after 100ms
attempts++;
setTimeout(identifyUser, 100);
} else {
console.error('Checkpoint failed to load after 5 seconds');
}
}
identifyUser();
}
// When user logs out
function handleUserLogout() {
if (window.Checkpoint) {
window.Checkpoint.reset();
console.log('User identification reset');
}
}Important: Always check if window.Checkpoint exists before calling methods. The pixel script loads asynchronously.
For Next.js apps with NextAuth, create a component to handle identification:
// app/components/analytics-provider.tsx
'use client';
import { useEffect, useRef } from 'react';
import { useSession } from 'next-auth/react';
// TypeScript type definitions
declare global {
interface Window {
Checkpoint?: {
identify: (userId: string, traits?: Record<string, any>) => void;
reset: () => void;
getUser: () => { id: string } | null;
track: (event: string, data?: Record<string, any>) => void;
};
}
}
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession();
const lastUserIdRef = useRef<string | null>(null);
useEffect(() => {
// Wait for session to load
if (status === 'loading') return;
if (status === 'authenticated' && session?.user?.id) {
const userId = session.user.id;
// Only identify if user has changed (prevents duplicate calls)
if (userId !== lastUserIdRef.current) {
identifyUser(userId, {
email: session.user.email,
name: session.user.name,
});
lastUserIdRef.current = userId;
}
} else if (status === 'unauthenticated') {
// Reset on logout (only if we had a previous user)
if (lastUserIdRef.current && typeof window !== 'undefined' && window.Checkpoint) {
window.Checkpoint.reset();
lastUserIdRef.current = null;
}
}
}, [session, status]);
return <>{children}</>;
}
// Helper function with retry logic
function identifyUser(userId: string, traits?: Record<string, any>) {
// SSR safety check
if (typeof window === 'undefined') return;
let attempts = 0;
const maxAttempts = 50; // 50 seconds max wait time
const tryIdentify = () => {
if (window.Checkpoint) {
try {
window.Checkpoint.identify(userId, traits);
console.log('[Checkpoint] User identified:', userId);
} catch (error) {
console.error('[Checkpoint] Identification failed:', error);
}
} else if (attempts < maxAttempts) {
// Pixel not loaded yet, retry after delay
attempts++;
setTimeout(tryIdentify, 1000);
} else {
console.error('[Checkpoint] Failed to load after 50 seconds');
}
};
tryIdentify();
}Then add it to your layout:
// app/layout.tsx
import Script from 'next/script';
import { AnalyticsProvider } from './components/analytics-provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Script
src="https://kya.vouched.id/pixel.js"
data-project-id="YOUR_PROJECT_ID"
strategy="afterInteractive"
/>
<AnalyticsProvider>
{children}
</AnalyticsProvider>
</body>
</html>
);
}Best Practices Used:
- ✅ SSR safety check (
typeof window === 'undefined') - ✅ Retry logic for async pixel loading
- ✅ Deduplication via refs (prevents duplicate identify calls)
- ✅ Error handling
- ✅ Proper cleanup on logout
// components/UserIdentification.jsx
import { useEffect } from 'react';
import { useAuth } from './auth-context';
export function UserIdentification() {
const { user, isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated && user) {
let attempts = 0;
const maxAttempts = 50; // Try for up to 5 seconds
const tryIdentify = () => {
if (window.Checkpoint) {
window.Checkpoint.identify(user.id, {
email: user.email,
name: user.displayName,
plan: user.subscription,
registeredAt: user.createdAt,
});
} else if (attempts < maxAttempts) {
attempts++;
setTimeout(tryIdentify, 100);
} else {
console.error('Checkpoint failed to load after 5 seconds');
}
};
tryIdentify();
} else if (!isAuthenticated && window.Checkpoint) {
// Reset on logout
window.Checkpoint.reset();
}
}, [user, isAuthenticated]);
return null;
}Viewing Users in the Dashboard
After successfully implementing user identification, you'll see user information displayed throughout your Checkpoint dashboard.
Activity Feed
Once a user is identified, their information appears as a blue badge next to each session in the Activity feed, following the same email > userId > anonymous attribution fallback used throughout the dashboard:
- Email badge: Shows
👤 john.doe@example.comif email trait was provided - User ID badge: Shows
👤 user-123if only userId was provided (no email)
See Analytics & Reporting for the full field reference for the Activity feed and its legacy Monitor (legacy) tab.
Quick Find: Look for the blue badge with a user icon (👤) next to a session. This badge only appears for identified users.
Session Details Panel
When you expand a session row, the details panel shows:
- User ID: Displayed in the connection information
- Email: If provided in user traits
- Name: If provided in user traits
- Custom traits: Additional properties you sent via
identify()
Verification Steps
After implementing identify() on your site, verify it's working:
- Implement
identify()on your site (see Implementation section above) - Log in to your site (or have ChatGPT/AI agent log in)
- Navigate a few pages to generate detections
- Wait 5-10 seconds for data to sync
- Open the Checkpoint dashboard → Activity
- Look for the blue user badge (👤) in the session row
- Expand the session to see full user details in the Session Details panel
What You Should See
If identification is working:
✅ Blue user badge appears next to the session's pixel/domain name ✅ Badge shows email (preferred) or userId ✅ Session Details panel displays userId
If NOT working:
❌ No blue user badge visible ❌ Session Details shows no userId
→ See Troubleshooting section below for solutions
Integration with Analytics Platforms
Amplitude Integration
Sync user identification between Checkpoint and Amplitude for unified analytics:
// Initialize both libraries
import * as amplitude from '@amplitude/analytics-browser';
// When user logs in
function identifyUser(user) {
const userId = user.id;
const userTraits = {
email: user.email,
name: user.name,
plan: user.plan,
company: user.company,
};
// Identify with Checkpoint
if (window.Checkpoint) {
window.Checkpoint.identify(userId, userTraits);
}
// Identify with Amplitude
amplitude.identify(userId, userTraits);
// Track login event
amplitude.track('User Logged In', {
source: 'web',
method: user.authMethod,
});
}
// Listen for Checkpoint identification events
window.addEventListener('checkpoint:identify', (event) => {
// Sync with Amplitude when Checkpoint identifies a user
const { userId, traits } = event.detail;
amplitude.identify(userId, traits);
});Google Tag Manager Integration
For GTM-based deployments, Checkpoint ships an official Checkpoint Pixel template in the Community Template Gallery (publisher: Know-That-Ai — older gallery listings may still show the template's previous name, AgentShield, until Google syncs the rename). The template handles the pixel loader; the identification snippet below runs as a Custom HTML tag triggered by your user_login event. See the GTM + Next.js integration guide for the full step-by-step.
Push user identification to GTM's data layer:
// Enhanced identification with GTM
function identifyUserWithGTM(user) {
// Identify with Checkpoint
if (window.Checkpoint) {
window.Checkpoint.identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan,
});
}
// Push to GTM data layer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'user_identified',
user_id: user.id,
user_properties: {
email: user.email,
name: user.name,
plan: user.plan,
identified_via: 'checkpoint',
},
});
}
// Listen for Checkpoint events
window.addEventListener('checkpoint:identify', (event) => {
window.dataLayer.push({
event: 'checkpoint_user_identified',
...event.detail,
});
});API Reference
This section covers the window.Checkpoint surface as used for user identification: identify,
getUser, reset, and track. For the complete JavaScript API — including getSession(),
grantConsent(), getInitInfo(), and lastDetection — see Marketing Pixel: JavaScript
API.
window.Checkpoint.identify(userId, traits)
Identifies a user and associates them with the current session. This call is rate-limited — see the Pixel JavaScript API for details.
Parameters:
userId(string, required): Unique identifier for the usertraits(object, optional): Additional user properties
Important Notes:
- User ID is stored in cookies for persistent identification (requires user consent under GDPR)
- User traits (email, name, etc.) are sent to the server and NOT stored in cookies
- Call this method when users log in or when you want to identify a session
Example:
window.Checkpoint.identify('user_123', {
email: 'john@example.com',
name: 'John Doe',
plan: 'premium',
company: 'Acme Corp',
});Privacy Note: User traits are sent to Checkpoint servers and stored in your database. Never send sensitive information like passwords or credit card numbers.
window.Checkpoint.getUser()
Returns the currently identified user or null if no user is identified.
Returns:
{
id: 'user_123';
}
// or null if not identifiedNote: User traits are not returned by this method (they're stored server-side only).
Example:
const user = window.Checkpoint.getUser();
if (user) {
console.log('Current user:', user.id);
} else {
console.log('No user identified');
}window.Checkpoint.reset()
Clears the current user identification and starts a new anonymous session. Call this when users log out.
What it does:
- Clears user ID from cookies
- Generates new session ID
- Generates new device ID
- Sends logout event to server
Example:
// On user logout
window.Checkpoint.reset();
console.log('User identification cleared');window.Checkpoint.track(eventName, data)
Track custom events for analytics and detection.
Parameters:
eventName(string, required): Name of the eventdata(object, optional): Additional event data
Example:
// Track form submission
window.Checkpoint.track('form_submit', {
form_id: 'contact-form',
page: window.location.pathname,
});
// Track button click
window.Checkpoint.track('button_click', {
button: 'pricing-cta',
plan: 'enterprise',
});Events
checkpoint:identify
Fired when a user is identified. Useful for syncing with other analytics tools. The legacy
agentshield:identify event still fires as a deprecated alias.
window.addEventListener('checkpoint:identify', (event) => {
console.log('User identified:', event.detail);
// event.detail contains: { userId, traits, sessionId }
});checkpoint:reset
Fired when user identification is reset (logout). The legacy agentshield:reset event still fires
as a deprecated alias.
window.addEventListener('checkpoint:reset', (event) => {
console.log('User reset:', event.detail);
// event.detail contains: { previousUserId, newSessionId }
});Testing with AI Agents
ChatGPT Agent Mode Testing
To test user identification with ChatGPT's agent mode, you'll need to implement a login flow that ChatGPT can navigate.
- Implement a test login endpoint:
// pages/api/test-login.js (Next.js example)
export default function handler(req, res) {
// Simple test authentication
if (req.method === 'POST') {
const { username, password } = req.body;
if (username === 'test' && password === 'test123') {
// Set session/cookie
res.status(200).json({
success: true,
user: {
id: 'test_user_123',
email: 'test@example.com',
name: 'Test User',
plan: 'premium',
},
});
}
}
}- Create a login page ChatGPT can navigate:
<!-- public/login.html -->
<form id="login-form">
<input type="text" id="username" placeholder="Username" />
<input type="password" id="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<script>
document.getElementById('login-form').onsubmit = async (e) => {
e.preventDefault();
const response = await fetch('/api/test-login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById('username').value,
password: document.getElementById('password').value,
}),
});
if (response.ok) {
const { user } = await response.json();
// Identify with Checkpoint
if (window.Checkpoint) {
window.Checkpoint.identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan,
});
console.log('User identified:', user.id);
alert('Login successful! User identified.');
}
}
};
</script>- Test flow with ChatGPT:
- Ask ChatGPT: "Visit [your-site-url] and log in with username 'test' and password 'test123'"
- ChatGPT will navigate to your site (tracked as anonymous session)
- ChatGPT will fill in the login form
- Upon successful login, the user will be identified
- Check your Checkpoint dashboard to see the userId associated with the ChatGPT session
Privacy and Security
Important Security Considerations: - Never send sensitive information (passwords, SSNs, etc.)
as user traits - User traits are stored in your database and may be visible in logs - Ensure you
comply with GDPR/CCPA when tracking user data - Implement proper authentication before calling
identify()
Best Practices
- Verify authentication server-side before identifying users
- Use consistent user IDs across all your analytics platforms
- Include relevant traits that help with segmentation and analysis
- Call reset() on logout to properly end the identified session
- Test thoroughly with both human users and AI agents
Troubleshooting
window.Checkpoint is undefined
Cause: The pixel script hasn't loaded yet when you're trying to call identify().
Solution: Use retry logic:
function waitForCheckpoint(callback, maxRetries = 50) {
if (window.Checkpoint) {
callback();
} else if (maxRetries > 0) {
setTimeout(() => waitForCheckpoint(callback, maxRetries - 1), 100);
} else {
console.error('Checkpoint failed to load after 5 seconds');
}
}
// Usage
waitForCheckpoint(() => {
window.Checkpoint.identify('user_123', { email: 'user@example.com' });
});User badge not appearing in dashboard
Problem: You called identify() but don't see the blue user badge (👤) in the Activity feed.
Troubleshooting steps:
-
Verify identify() was called successfully:
console.log('Current user:', window.Checkpoint?.getUser()); // Should output: { id: 'your-user-id' } // If null, identify() wasn't called or failed -
Check the pixel is sending userId in requests:
- Open browser DevTools → Network tab
- Filter for requests to
kya.vouched.id - Look for POST request to
/api/v1/pixel - Inspect Request Payload - should include
userIdfield - If userId is missing, the identify() call didn't work
-
Verify timing of identify() call:
identify()must be called BEFORE navigation/pageview events- Dashboard refreshes every 5 seconds when auto-refresh is enabled
- Try manually refreshing the dashboard after 10 seconds
- If you identified AFTER the pageview was tracked, subsequent pages should show the user badge
-
Check you're viewing the correct project:
- Verify the project ID in your pixel script matches the dashboard project
- Compare with
data-project-idin your pixel script (find both under Installations — see Credentials)
-
Check browser console for errors:
- Look for CSP violations blocking the pixel
- Look for network errors to
kya.vouched.id - Look for JavaScript errors when calling
identify()
-
Test with console script:
Open your site and test identification manually:
// Step 1: Verify pixel is loaded console.log('Pixel loaded:', typeof window.Checkpoint !== 'undefined'); // Step 2: Identify a test user window.Checkpoint.identify('test-user-123', { email: 'test@example.com', name: 'Test User', }); // Step 3: Verify user was set console.log('User set:', window.Checkpoint.getUser()); // Should output: { id: 'test-user-123' } // Step 4: Navigate to another page (or reload) window.location.reload(); // Step 5: Check dashboard after 10 seconds -
Verify in the dashboard:
If the console checks above pass but the badge still doesn't appear, confirm the data is actually reaching your project:
- Open Activity and look for a session at the timestamp of your test — if it's missing entirely, the pixel isn't reaching your project (recheck Installations for the correct Project ID; see Credentials)
- If the session is there but has no user badge, the request reached Checkpoint without the
userId— re-check the Network tab payload from step 2 above
Common Issue: If you identify the user AFTER the first pageview is sent, the first detection won't have the userId. Navigate to another page, and subsequent detections should show the user badge.
User not showing in dashboard
Troubleshooting steps:
-
Check pixel is loaded:
console.log('Pixel loaded:', typeof window.Checkpoint !== 'undefined'); console.log('Init info:', window.Checkpoint?.getInitInfo()); -
Verify identification was called:
console.log('Current user:', window.Checkpoint?.getUser()); -
Check browser console for errors:
- Look for CSP violations
- Look for network errors to
kya.vouched.id - Look for JavaScript errors
-
Verify project ID matches your dashboard
-
Wait 1-2 minutes - Data can take time to appear
Duplicate identify() calls
Cause: Identify is being called multiple times on the same page/session.
Solution: Use deduplication pattern:
let lastIdentifiedUser = null;
function identifyIfChanged(userId, traits) {
if (userId !== lastIdentifiedUser) {
window.Checkpoint?.identify(userId, traits);
lastIdentifiedUser = userId;
}
}Or in React/Next.js, use useRef:
const lastUserIdRef = useRef(null);
if (userId !== lastUserIdRef.current) {
window.Checkpoint?.identify(userId, traits);
lastUserIdRef.current = userId;
}Session not persisting across page reloads
Causes:
- Cookies are blocked
- Third-party cookie restrictions
- Incognito/private browsing mode
Solutions:
-
Check cookies are enabled:
console.log('Cookies enabled:', navigator.cookieEnabled); -
Check for cookie consent - If you're using
data-require-consent="true", make sure to call:window.Checkpoint.grantConsent(); -
Inspect cookies in DevTools - Look for the
checkpoint_usercookie. You should seeagentshield_useralongside it with the same contents — the pixel writes both for back-compat, so either name confirms identity is persisting
TypeScript errors
Error: Property 'Checkpoint' does not exist on type 'Window'
Solution: Add type declarations:
declare global {
interface Window {
Checkpoint?: {
identify: (userId: string, traits?: Record<string, any>) => void;
reset: () => void;
getUser: () => { id: string } | null;
getSession: () => { id: string; startTime: number; duration: number; userId: string | null };
track: (event: string, data?: Record<string, any>) => void;
grantConsent: () => void;
getInitInfo: () => object;
};
}
}Next.js "window is not defined" error
Cause: Code is running on the server during SSR.
Solution: Add SSR safety check:
if (typeof window !== 'undefined' && window.Checkpoint) {
window.Checkpoint.identify(userId, traits);
}Or use 'use client' directive in components that use window.Checkpoint.
Example: Complete Next.js Integration
Here's a complete example integrating Checkpoint with Amplitude in a Next.js app:
// app/providers/analytics-provider.tsx
'use client';
import { useEffect } from 'react';
import { useSession } from 'next-auth/react';
import * as amplitude from '@amplitude/analytics-browser';
const AMPLITUDE_API_KEY = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!;
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession();
useEffect(() => {
// Initialize Amplitude
amplitude.init(AMPLITUDE_API_KEY);
// Set up Checkpoint event listeners
const handleIdentify = (event: CustomEvent) => {
const { userId, traits } = event.detail;
amplitude.identify(userId, traits);
amplitude.track('User Identified via Checkpoint', {
sessionId: event.detail.sessionId
});
};
const handleReset = () => {
amplitude.reset();
};
window.addEventListener('checkpoint:identify', handleIdentify as EventListener);
window.addEventListener('checkpoint:reset', handleReset);
return () => {
window.removeEventListener('checkpoint:identify', handleIdentify as EventListener);
window.removeEventListener('checkpoint:reset', handleReset);
};
}, []);
useEffect(() => {
if (status === 'authenticated' && session?.user) {
const userId = session.user.id;
const traits = {
email: session.user.email,
name: session.user.name,
};
// Identify with both platforms
if (window.Checkpoint) {
window.Checkpoint.identify(userId, traits);
}
amplitude.identify(userId, traits);
} else if (status === 'unauthenticated') {
// Reset both platforms
if (window.Checkpoint) {
window.Checkpoint.reset();
}
amplitude.reset();
}
}, [session, status]);
return <>{children}</>;
}This completes the user identification feature, allowing you to track authenticated users across AI agent sessions!
