Workspace Link: TZI - Temp Workspace
You can view the workspace and test the changes yourself if you'd like.
The implementation adds minimal complexity to your existing GTM setup while solving the conversion inflation problem.
This is the core logic variable that implements the once-per-session deduplication.
What it does:
The code:
function() {
var type = '';
var identifier = '';
var gtmEvent = {{Event}};
// VIDEO
if (gtmEvent === 'gtm.video') {
type = 'video';
var videoUrl = {{Video URL}} || '';
var cleanVideo = videoUrl
.replace(/^https?:\/\/(www\.)?/i, '');
identifier = cleanVideo + '_' + {{Video Percent}};
// SCROLL
} else if (gtmEvent === 'gtm.scrollDepth') {
type = 'scroll';
identifier = {{Scroll Depth Threshold}};
// LINK CLICK
} else if (gtmEvent === 'gtm.linkClick') {
type = 'link_click';
var clickUrl = {{Click URL}} || '';
var rootDomain = window.location.hostname;
var isInternal = clickUrl.indexOf(rootDomain) !== -1;
var newWinEntry = window.dataLayer
.slice()
.reverse()
.find(function(obj) {
return obj['gtm.willOpenInNewWindow'] !== undefined;
});
var newWindow = newWinEntry
? newWinEntry['gtm.willOpenInNewWindow']
: false;
identifier = (newWindow && !isInternal) ? clickUrl : {{Click Text}};
} else {
return false;
}
// GUARD — nothing to key on
if (identifier === undefined || identifier === null || identifier === '') {
return false;
}
var normalized = identifier
.toString()
.toLowerCase()
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-z0-9_\-\/]/g, '');
var key = 'session_event_' + type + '_' + normalized;
if (sessionStorage.getItem(key)) {
return false;
}
sessionStorage.setItem(key, 'true');
return true;
}
How it works:
When an event fires, this variable:
gtm.willOpenInNewWindow from dataLayer)false (block the event)true (allow the event)Example session keys:
session_event_link_click_give_it_a_try session_event_video_youtubecom/watchvengw7tlk6r8_90 session_event_video_youtubecom/watchvengw7tlk6r8_100 session_event_scroll_50 session_event_link_click_https//linkedincom/
Note: Special characters are removed from session keys for safety and consistency.
This is a data layer variable that determines if a button/link will open in a new window.
What it does:
_blankNote: This is an out-of-the-box GTM variable type. No custom JavaScript is needed - GTM provides this functionality natively.
The following 12 triggers were updated with one additional condition:
The change: Added one condition to each trigger:
Fire Once Per Session = true
This ensures that each event only fires once per session, preventing conversion inflation while preserving analytics visibility.
This solution is elegant because:
{{Fire Once Per Session}} to any future GA4 or other vendor pixels and it will work for any click/video/scroll trigger in GTM
GA4 video events will fire once per video per segment.
This should be fine as all the values sent to Google Ads will still be unique if you're sending video title and progress to Google Ads. Each video engagement will be tracked separately based on the video URL and percentage watched.