Checkpoint Docs

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.Checkpoint only exists once pixel.js has loaded.
  • Your Project ID — see Credentials.
  • A userId for 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:

  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.

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.com if email trait was provided
  • User ID badge: Shows 👤 user-123 if 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:

  1. Implement identify() on your site (see Implementation section 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

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 user
  • traits (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 identified

Note: 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 event
  • data (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.

  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. You should see agentshield_user alongside 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!