AgentShield
Server Integrations

Next.js + Google Tag Manager Integration

Complete guide to integrate AgentShield user identification with Next.js and Google Tag Manager

Overview

This guide shows how to integrate AgentShield with a Next.js website using Google Tag Manager (GTM) to track user identification. When users log into your site, their userId will appear in the AgentShield detection monitor, matching your internal user system.

Goal: When a user logs in as userId: "user_123" on your site, AgentShield will track them with the same userId: "user_123" so you can correlate your user data with AI agent detection.

How It Works

  1. User visits your site → GTM loads AgentShield pixel
  2. User logs in → Your Next.js app pushes user data to GTM dataLayer
  3. GTM detects login → Triggers AgentShield.identify() with user data
  4. AgentShield tracks → All subsequent activity shows with userId and email
  5. Detection monitor → Shows the same userId from your system

Step 1: GTM Setup

1.1 Create Variables in GTM

Go to VariablesUser-Defined VariablesNew

Variable 1: User ID

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: userId
  • Variable Name: DLV - User ID

Variable 2: User Email

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: userEmail
  • Variable Name: DLV - User Email

Variable 3: User Name (Optional)

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: userName
  • Variable Name: DLV - User Name

1.2 Create AgentShield Pixel Tag

Go to TagsNew

  • Tag Type: Custom HTML
  • Tag Name: "AgentShield - Pixel Loader"
  • HTML:
<script>
  (function () {
    // Prevent duplicate loading
    if (window.AgentShieldInitialized) {
      console.log('[GTM] AgentShield already loaded, skipping');
      return;
    }
    window.AgentShieldInitialized = true;

    var as = document.createElement('script');
    as.type = 'text/javascript';
    as.async = true;
    as.src = 'https://kya.vouched.id/pixel.js';
    as.setAttribute('data-project-id', 'YOUR_PROJECT_ID');
    as.setAttribute('data-debug', 'true'); // Remove in production
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(as, s);
  })();
</script>
  • Triggering: All Pages
  • Tag Firing Options: Once per page
Replace YOUR_PROJECT_ID with your actual AgentShield project ID.

1.3 Create User Identification Tag

Go to TagsNew

  • Tag Type: Custom HTML
  • Tag Name: "AgentShield - User Identification"
  • HTML:
<script>
  (function() {
    // Wait for AgentShield to be available with retry logic
    function identifyUser(retries) {
      retries = retries || 0;

      if (window.AgentShield) {
        var userId = {{DLV - User ID}};
        var userEmail = {{DLV - User Email}};
        var userName = {{DLV - User Name}};

        if (userId) {
          try {
            window.AgentShield.identify(userId, {
              email: userEmail,
              name: userName
            });
            console.log('[GTM] AgentShield user identified:', userId);
          } catch (error) {
            console.error('[GTM] Failed to identify user:', error);
          }
        } else {
          console.warn('[GTM] No userId provided to identify');
        }
      } else if (retries < 50) {
        // Retry up to 50 times (5 seconds total)
        setTimeout(function() { identifyUser(retries + 1); }, 100);
      } else {
        console.error('[GTM] AgentShield failed to load after 5 seconds');
      }
    }

    identifyUser();
  })();
</script>
  • Triggering: Custom Event
  • Event Name: user_login

Step 2: Next.js Integration

If you have Content Security Policy (CSP) configured, you must allowlist https://kya.vouched.id in both script-src and connect-src directives. See the CSP configuration example below.

2.1 Add GTM to Your Next.js App

// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        {/* Google Tag Manager */}
        <Script id="gtm" strategy="afterInteractive">
          {`
            (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','dataLayer','GTM-XXXXXXX');
          `}
        </Script>
      </head>
      <body>
        {/* GTM NoScript */}
        <noscript>
          <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
            height="0" width="0" style={{display: 'none', visibility: 'hidden'}} />
        </noscript>
        
        {children}
      </body>
    </html>
  );
}
// pages/_app.tsx
import Script from 'next/script';

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      {/* Google Tag Manager */}
      <Script id="gtm" strategy="afterInteractive">
        {`
          (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
          new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
          j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
          'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
          })(window,document,'script','dataLayer','GTM-XXXXXXX');
        `}
      </Script>
      
      <Component {...pageProps} />
    </>
  );
}
Replace GTM-XXXXXXX with your actual GTM container ID.

2.2 Configure CSP Headers (If Applicable)

If your Next.js site has Content Security Policy headers, add AgentShield domain:

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://kya.vouched.id https://www.googletagmanager.com",
              "connect-src 'self' https://kya.vouched.id https://www.google-analytics.com",
              // ... your other CSP directives
            ].join('; '),
          },
        ],
      },
    ];
  },
};

Most Next.js sites don't have CSP configured by default. Only add this if you're seeing CSP errors in your browser console.

2.3 Push User Data to GTM DataLayer

Add this code after successful user login:

// components/login-form.tsx or pages/api/auth/[...nextauth].ts
import { signIn, useSession } from 'next-auth/react';

function LoginForm() {
  const { data: session } = useSession();
  
  // Trigger identification when session is established
  useEffect(() => {
    if (session?.user) {
      // Push user data to GTM dataLayer
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'user_login',
        userId: session.user.id,
        userEmail: session.user.email,
        userName: session.user.name,
      });
    }
  }, [session]);
  
  return (
    <button onClick={() => signIn()}>
      Sign In
    </button>
  );
}
// utils/auth.ts
export async function handleLogin(email: string, password: string) {
  try {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });
    
    const user = await response.json();
    
    if (user.success) {
      // Store user session (localStorage, cookies, etc.)
      localStorage.setItem('user', JSON.stringify(user.data));
      
      // Push to GTM dataLayer
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'user_login',
        userId: user.data.id,
        userEmail: user.data.email,
        userName: user.data.name,
      });
      
      // Redirect to dashboard
      router.push('/dashboard');
    }
  } catch (error) {
    console.error('Login failed:', error);
  }
}
// components/auth0-wrapper.tsx
import { useUser } from '@auth0/nextjs-auth0/client';
import { useEffect } from 'react';

export function Auth0Wrapper({ children }) {
  const { user, isLoading } = useUser();
  
  useEffect(() => {
    if (user && !isLoading) {
      // Push Auth0 user to GTM
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'user_login',
        userId: user.sub, // Auth0 user ID
        userEmail: user.email,
        userName: user.name,
      });
    }
  }, [user, isLoading]);
  
  return children;
}
// components/clerk-wrapper.tsx
import { useUser } from '@clerk/nextjs';
import { useEffect } from 'react';

export function ClerkWrapper({ children }) {
  const { user, isSignedIn } = useUser();
  
  useEffect(() => {
    if (isSignedIn && user) {
      // Push Clerk user to GTM
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'user_login',
        userId: user.id,
        userEmail: user.emailAddresses[0]?.emailAddress,
        userName: user.fullName,
      });
    }
  }, [user, isSignedIn]);
  
  return children;
}

2.4 Handle User Logout

When users log out, reset their identification:

// utils/auth.ts or logout handler
function handleLogout() {
  // Reset AgentShield identification first
  if (typeof window !== 'undefined' && window.AgentShield) {
    window.AgentShield.reset();
  }

  // Push logout event to GTM (optional)
  if (typeof window !== 'undefined') {
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'user_logout',
    });
  }

  // Clear user session
  localStorage.removeItem('user');

  // Redirect to login
  router.push('/login');
}

Always call window.AgentShield.reset() on logout to properly clear user identification and start a new anonymous session.

Step 3: Testing & Verification

3.1 Enable GTM Preview Mode

  1. In GTM, click PreviewStart debugging
  2. Enter your website URL
  3. Navigate to your site with the debug panel open

3.2 Test the Flow

  1. Visit your site → Check GTM debug:

    • ✅ "AgentShield - Pixel Loader" should fire on page load
    • ✅ Browser console should show: [AgentShield] Pixel loaded for project: YOUR_PROJECT_ID
  2. Log into your site → Check GTM debug:

    • ✅ DataLayer should show user_login event with userId, userEmail
    • ✅ "AgentShield - User Identification" tag should fire
    • ✅ Browser console should show: [GTM] AgentShield user identified: user_123, user@example.com
  3. Navigate around the site → Generate activity while logged in

  4. Check AgentShield Dashboard:

    • Go to your project's Detection Monitor
    • Look for new detections
    • ✅ Should show userId: "user_123" and email badge

3.3 Browser Console Verification

Open browser DevTools and run:

// Check if AgentShield is loaded
console.log('AgentShield loaded:', !!window.AgentShield);

// Check current user
console.log('Current user:', window.AgentShield?.getUser());

// Check GTM dataLayer
console.log('DataLayer:', window.dataLayer);

Step 4: Production Deployment

4.1 Remove Debug Mode

  1. In GTM: Remove data-debug="true" from pixel loader tag
  2. In Next.js: Remove any debug console.log statements
  3. Publish your GTM container changes

4.2 Monitor in Production

  • Check AgentShield detection monitor regularly
  • Verify userIds match your internal user system
  • Monitor for any identification gaps or issues

Common Issues & Solutions

Issue: "AgentShield not defined" Error

Cause: The pixel script hasn't loaded yet when identify() is called.

Solution: The retry logic in the User Identification tag (above) already handles this. If you're still seeing this error:

  1. Check GTM tag firing order:

    • Pixel Loader tag should fire on "All Pages"
    • User Identification tag should fire on custom event user_login
    • Not both on same trigger
  2. Verify pixel is loading:

    // In browser console
    console.log('Pixel loaded:', typeof window.AgentShield !== 'undefined');
  3. Check for CSP errors - See Next.js CSP configuration above

  4. Increase retry timeout if your site is slow:

    // Change from 50 retries (5s) to 100 retries (10s)
    } else if (retries < 100) {

Issue: DataLayer Variables Empty

Cause: User data not pushed to dataLayer before GTM tag fires.

Solution: Ensure dataLayer.push happens before navigation/page changes.

Issue: Multiple Identifications

Cause: GTM tag firing multiple times.

Solution: Use "Once per event" trigger setting or add fired flag:

<script>
  if (!window._agentShieldIdentified) {
    window._agentShieldIdentified = true;
    // Your identification code here
  }
</script>

Issue: UserIds Not Showing in Dashboard

Troubleshooting Steps:

  1. Check GTM Preview - is the tag firing?
  2. Check browser console - any errors?
  3. Verify project ID matches your dashboard
  4. Check if identify() is actually being called
  5. Wait 1-2 minutes for data to appear

Advanced Configuration

Custom User Properties

Add more user data for richer tracking:

window.dataLayer.push({
  event: 'user_login',
  userId: user.id,
  userEmail: user.email,
  userName: user.name,
  userPlan: user.subscription_plan,
  userCompany: user.company,
  userRole: user.role,
  userSignupDate: user.created_at,
});

E-commerce Integration

For e-commerce sites, also identify users during checkout:

// On order completion
window.dataLayer.push({
  event: 'purchase',
  userId: customer.id,
  userEmail: customer.email,
  transactionId: order.id,
  value: order.total,
});

Summary

After completing this integration:

  1. AgentShield pixel tracks all visitors to your Next.js site
  2. User identification happens automatically when users log in via GTM
  3. Same userId appears in both your system and AgentShield dashboard
  4. Detection monitor shows user emails and IDs for easy correlation
  5. AI agent activity can be linked back to real users in your system

Your AgentShield detection monitor will now show entries like:

  • Human Activity: userId: "user_123" with email badge user@example.com
  • AI Agent Activity: userId: "user_123" with email badge user@example.com

This allows you to see when real users are browsing vs when AI agents visit on their behalf, all tied to the same user identity.

Command Palette

Search for a command to run...