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
- User visits your site → GTM loads the AgentShield pixel via the official template tag
- User logs in → Your Next.js app pushes user data to GTM dataLayer
- GTM detects login → Triggers AgentShield.identify() with user data
- AgentShield tracks → All subsequent activity shows with userId and email
- Detection monitor → Shows the same userId from your system
Prerequisites: Install the AgentShield template
The AgentShield pixel is published as an official Community Template in the GTM gallery. Install it once per GTM container before creating any tags.
One-time install per container. Once added to a workspace, the template appears under Tag Configuration → Custom and is reusable across as many tags as you need.
Open the template gallery
In your GTM container, go to Templates (left rail) → Tag Templates → Search Gallery.
Or open the template directly: tagmanager.google.com/gallery/.../agentshield-gtm-template.
Add to workspace
Search for AgentShield (publisher: Know-That-Ai). Click the result, then Add to workspace.
GTM will show a permissions summary — the template requests Inject scripts (to load pixel.js) and access to the https://kya.vouched.id host. Review and accept.
Confirm install
Back in Templates → Tag Templates, you should now see AgentShield Pixel listed under your workspace's templates. You're ready to create tags.
Template source / issues / contributions: the template is open source at
github.com/Know-That-Ai/agentshield-gtm-template.
Inspect the template.tpl source, file an issue, or open a PR there.
If your organization disables Community Templates, see the Alternate installation
paths at the bottom of this page. The recommended fallback is to
import the same template.tpl directly from the GitHub repo — same template, same fields, no
gallery dependency. A Custom HTML option is also documented as a last resort.
Step 1: GTM Setup
1.1 Create dataLayer Variables
Go to Variables → User-Defined Variables → New and create these three variables. The User Identification tag (Step 1.3) reads from them.
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 the AgentShield Pixel tag (using the template)
Go to Tags → New → Tag Configuration → Custom → AgentShield Pixel.
Configure these fields:
| Field | Required | Default | Notes |
|---|---|---|---|
| Project ID | Yes | — | Your AgentShield project ID (find it in your dashboard project settings) |
| API Endpoint | No | Auto-detected | Leave blank unless directed by support |
| Debug Mode | No | false | Turn on for testing; turn off before publishing |
| Session Timeout (ms) | No | 1800000 | 30 minutes |
| Respect Do Not Track | No | true | Honors browser DNT setting |
| Batch Size | No | 10 | Events per batch |
| Flush Interval (ms) | No | 5000 | How often batched events are sent |
| Enable Fingerprinting | No | true | Advanced browser detection |
- Tag Name:
AgentShield - Pixel Loader - Triggering: All Pages
- Tag Firing Options: Once per page
Replace the Project ID field with your actual AgentShield project ID before saving. The template enforces this as a required field.
1.3 Create the User Identification tag
The template handles the pixel loader. User identification still uses a Custom HTML tag because it needs to read your dataLayer variables and call AgentShield.identify() after login events. We may publish a dedicated Identify template in a future release; for now this Custom HTML is the canonical path.
Go to Tags → New:
- 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 - Tag Sequencing (optional but recommended): Set this tag to fire after "AgentShield - Pixel Loader" so the retry loop usually completes on the first attempt.
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} />
</>
);
}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
- In GTM, click Preview → Start debugging
- Enter your website URL
- Navigate to your site with the debug panel open
3.2 Test the Flow
-
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(only if Debug Mode is enabled on the template)
-
Log into your site → Check GTM debug:
- ✅ DataLayer should show
user_loginevent withuserId,userEmail - ✅ "AgentShield - User Identification" tag should fire
- ✅ Browser console should show:
[GTM] AgentShield user identified: user_123, user@example.com
- ✅ DataLayer should show
-
Navigate around the site → Generate activity while logged in
-
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 Turn off Debug Mode
- In GTM: Open the "AgentShield - Pixel Loader" tag and uncheck Debug Mode in the template configuration.
- In Next.js: Remove any debug
console.logstatements you added. - 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: Template not appearing in Tag Configuration
Cause: Template was added to a different workspace, or the install didn't complete.
Solution:
- Go to Templates → Tag Templates and confirm "AgentShield Pixel" is listed under Custom.
- If missing, click Search Gallery, search for AgentShield, and re-add it. Templates are scoped per workspace.
- If your organization disables community templates, use the Alternate installation paths instead — the direct
.tplimport from GitHub gives you the same template without going through the gallery.
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:
-
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 the same trigger
- Tag Sequencing on the Identification tag (run after Pixel Loader) helps
-
Verify pixel is loading:
// In browser console console.log('Pixel loaded:', typeof window.AgentShield !== 'undefined'); -
Check for CSP errors — see the Next.js CSP configuration above.
-
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 a fired flag:
<script>
if (!window._agentShieldIdentified) {
window._agentShieldIdentified = true;
// Your identification code here
}
</script>Issue: UserIds Not Showing in Dashboard
Troubleshooting Steps:
- Check GTM Preview — is the tag firing?
- Check browser console — any errors?
- Verify project ID in the template configuration matches your dashboard
- Check if
identify()is actually being called - 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,
});Alternate installation paths
The Community Template Gallery (covered in Prerequisites above) is the recommended install for most workspaces. If your environment can't use it — community templates disabled by org policy, air-gapped GTM workspace, regulated environment, etc. — pick one of the alternates below.
These paths only replace Step 1.2 (the Pixel tag). Steps 1.1 (dataLayer variables) and 1.3 (User Identification) are unchanged.
This path imports the same template from the GitHub repository — same display name, same 8 configuration fields, same behavior — without going through the gallery.
Download the template file
Grab template.tpl from github.com/Know-That-Ai/agentshield-gtm-template. Either:
- Click
template.tpl→ Raw → save astemplate.tpl, or git clone https://github.com/Know-That-Ai/agentshield-gtm-template.gitand usetemplate.tplfrom the clone
Import into GTM
In your GTM container: Templates → Tag Templates → New → ⋮ menu (top right) → Import. Select the template.tpl you downloaded.
Save and use
Click Save in the template editor. The template now appears under Custom in Tag Configuration with the same name (AgentShield Pixel) and field set as the gallery version. Continue with Step 1.2.
Updates: when the template is updated upstream, you'll need to re-download template.tpl and
re-import. Gallery installs auto-prompt for updates; direct imports don't. Watch the GitHub
repo's Releases if you go this
route.
Use this only if your environment disables all custom templates (gallery and direct import). Custom HTML loses the template's configurable parameters — you only get data-project-id and data-debug via script-tag attributes.
Go to Tags → New:
- Tag Type: Custom HTML
- Tag Name:
AgentShield - Pixel Loader (Manual) - 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
YOUR_PROJECT_ID with your actual AgentShield project ID.What you lose with Custom HTML: session timeout configuration, batch size, fingerprinting toggle, DNT honoring, custom API endpoint. These are surfaced as template fields in both gallery and direct-import paths but aren't accessible via script-tag attributes. If you can use the direct-import path instead, prefer that.
Summary
After completing this integration:
- ✅ AgentShield template installed once per GTM container
- ✅ AgentShield pixel tracks all visitors to your Next.js site
- ✅ User identification happens automatically when users log in via GTM
- ✅ Same userId appears in both your system and AgentShield dashboard
- ✅ Detection monitor shows user emails and IDs for easy correlation
- ✅ 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 badgeuser@example.com - AI Agent Activity:
userId: "user_123"with email badgeuser@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.