Checkpoint Docs
Cookbooks

Detect: Identify Users

Associate authenticated users on your website with their AI agent sessions

Goal

Associate authenticated users on your website with their AI agent sessions, using the pixel's identify() API. 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. By the end of this cookbook, you'll have:

  • The complete user journey tracked from AI agent discovery to conversion
  • Visibility into how authenticated users interact with AI agents
  • AI traffic segmented by user properties (plan, company, role, etc.)
  • Conversion funnels that include AI touchpoints

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.

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.Checkpoint only exists once pixel.js has loaded. New to the pixel? Start with the Pixel Quick Start.
  • Your Project ID — see Credentials.
  • A userId for the signed-in user, from your own auth or analytics stack.

Time Estimate

15 minutes


How It Works

  1. User visits your website with both the Checkpoint pixel and your analytics (Amplitude, GA4, etc.)
  2. User logs in to your website → Your analytics identifies them as userId: "abc123" with email "visitor@gmail.com"
  3. You sync the identification → Call Checkpoint.identify("abc123", { email: "visitor@gmail.com" })
  4. AI agent visits your website → Checkpoint tracks it with the same userId: "abc123"
  5. You see in Checkpoint dashboard → The detection shows userId: "abc123" and email: "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.


Steps

Wire identify() into your login flow

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

Sync with your analytics platform

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

For the full identify() / getUser() / reset() / track() reference, including the events the pixel dispatches, see the Pixel JavaScript API. To identify users via Google Tag Manager instead, see the GTM + Next.js guide.


Verify It's Working

After implementing identify() on your site, verify it's working:

  1. Implement identify() on your site (the steps above)
  2. Log in to your site (or have ChatGPT/AI agent log in)
  3. Navigate a few pages to generate detections
  4. Wait 5-10 seconds for data to sync
  5. Open the Checkpoint dashboard → Activity
  6. Look for the blue user badge (👤) in the session row
  7. Expand the session to see full user details in the Session Details panel

Where identified users surface in the dashboard (Activity badges, the Session Details panel) is covered in Identified Users.

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 below for solutions


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.

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

  1. Verify authentication server-side before identifying users
  2. Use consistent user IDs across all your analytics platforms
  3. Include relevant traits that help with segmentation and analysis
  4. Call reset() on logout to properly end the identified session
  5. 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:

  1. 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
  2. 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 userId field
    • If userId is missing, the identify() call didn't work
  3. 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
  4. Check you're viewing the correct project:

    • Verify the project ID in your pixel script matches the dashboard project
    • Compare with data-project-id in your pixel script (find both under Installations — see Credentials)
  5. 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()
  6. 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
  7. 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:

  1. Check pixel is loaded:

    console.log('Pixel loaded:', typeof window.Checkpoint !== 'undefined');
    console.log('Init info:', window.Checkpoint?.getInitInfo());
  2. Verify identification was called:

    console.log('Current user:', window.Checkpoint?.getUser());
  3. Check browser console for errors:

    • Look for CSP violations
    • Look for network errors to kya.vouched.id
    • Look for JavaScript errors
  4. Verify project ID matches your dashboard

  5. 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:

  1. Check cookies are enabled:

    console.log('Cookies enabled:', navigator.cookieEnabled);
  2. Check for cookie consent - If you're using data-require-consent="true", make sure to call:

    window.Checkpoint.grantConsent();
  3. Inspect cookies in DevTools - Look for the checkpoint_user cookie (a agentshield_user cookie from a returning visitor is also honored, 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}</>;
}

What You Learned

  • How to call window.Checkpoint.identify() on login, with retry and deduplication patterns for JavaScript, Next.js, and React
  • How to reset identification on logout with window.Checkpoint.reset()
  • How to keep Checkpoint and Amplitude identification in sync
  • How to verify identified users in the Activity feed and debug a missing user badge

Next Steps

GoalWhere to go
Full identification API referencePixel JavaScript API
Identify users via Google Tag ManagerGTM + Next.js guide
See identified users in the dashboardIdentified Users
Block or challenge detected agentsGateway Setup