Label invalid traffic with Transfon Gateway and exclude it from GA4 reports
Bots and invalid traffic inflate pageviews, wreck conversion rates, and make GA4 reports unreliable. GA4's built-in bot filtering only covers known crawlers on the IAB spiders list. It misses headless browsers, scrapers, and click bots. This guide shows how to load Google Tag Manager through the Transfon Gateway tag so every visitor is labeled with a traffic_type in the dataLayer before GTM loads, and GA4 can filter bots out.
window.waf360Load(trafficType) function.waf360Load() with the verdict: bot for flagged visitors, clean for everyone else.waf360Load() pushes a traffic_type event to the dataLayer, then loads GTM.traffic_type with every hit, which GA4 data filters can exclude.Two failsafes guarantee GTM always loads, even if Transfon Gateway is slow or unreachable: an onerror handler on the tag script and a timeout fallback. In both cases traffic is labeled clean.
Your standard GTM snippet looks like this:
<!-- Google Tag Manager -->
<script>(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>
<!-- End Google Tag Manager -->
Replace it with the following. Substitute GTM-XXXXXXX with your container ID and YOUR_LICENSE_KEY with your Transfon Gateway license key.
<!-- Google Tag Manager, loaded via Transfon Gateway -->
<script>
window.waf360Load=function(t){if(waf360Load.d)return;waf360Load.d=1;
window.dataLayer=window.dataLayer||[];
dataLayer.push({event:'traffic_type',traffic_type:t||'clean'});
!function(e,t,a,n){e[n]=e[n]||[],e[n].push({"gtm.start":(new Date).getTime(),event:"gtm.js"}),n=t.getElementsByTagName(a)[0],(a=t.createElement(a)).async=!0,a.src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX",n.parentNode.insertBefore(a,n)}(window,document,"script","dataLayer")
};setTimeout(waf360Load,1000);
</script>
<script id="waf360-tag" src="https://tag.waf360.com/tag/tag.js" data-license="YOUR_LICENSE_KEY" async
onerror="waf360Load()"></script>
<!-- End Google Tag Manager -->
What each piece does:
window.waf360Load(t) wraps the original GTM loader. It pushes a traffic_type event to the dataLayer first (using the value passed by the tag, or clean when called without one) and then loads GTM. The d flag makes it idempotent, so it only ever runs once.waf360Load(trafficType) automatically when its check finishes, so no _waf360.check() callback or queue stub is needed. Flagged visitors (blocked, or failing the quality check) are labeled bot; everyone else is labeled clean.onerror="waf360Load()": if the Transfon Gateway tag fails to load (network error, blocked), GTM loads immediately.setTimeout(waf360Load, 1000): if the check takes longer than 1 second, GTM loads anyway. Whichever path fires first wins; later calls are no-ops.<noscript> GTM iframe fallback (if you use it) can stay where it is unchanged.Note the trade-off in the timeout: if the check responds after 1 second, GTM has already loaded with traffic labeled clean. Raise the timeout if labeling accuracy matters more than analytics latency.
Optional attributes on the tag script:
data-sample-rate="0.2": run the verification on a sample of pageviews (here 20%) to reduce tag load on very high-traffic sites. On unsampled pageviews the timeout fallback fires and traffic is labeled clean, so use 1 (or omit the attribute) if you want every hit classified.traffic_type.clean as a safety net (every visitor is already labeled by waf360Load).DLV - traffic_type and save.traffic_type{{DLV - traffic_type}}Every GA4 hit now carries a traffic_type parameter: bot for flagged visitors, clean otherwise.
Exclude and Parameter value to bot.Test data filter name dimension instead of being dropped, so you can verify before excluding data permanently.Data excluded by an active filter is permanently removed from reports and cannot be backfilled. Always validate in Testing mode first.
traffic_type event appears in the dataLayer timeline and your Google Tag fires after it.traffic_type parameter.curl won't run JS, use Puppeteer or Chrome headless) and confirm traffic_type: bot is set.Instead of labeling bot traffic, you can stop GA4 (and any other tag) from firing at all:
traffic_type.DLV - traffic_type equals bot.This saves GA4 quota and third-party tag costs for bot traffic, at the price of losing visibility into how much bot traffic you receive. Labeling (Steps 2–4) is recommended if you want to monitor bot volume in reports; blocking is better if you are billed per event by third-party vendors.
The same approach works for any tag that isn't managed through GTM, such as a Meta Pixel, ad conversion pixels, an affiliate tracker, a session-recording script, or a gtag.js install without a container. Add the queue stub, then use window._waf360.check() to decide whether to load the tag:
<!-- Queue stub, then the Transfon Gateway tag -->
<script>window._waf360={q:[],send:(w,c)=>window._waf360.q.push([w,c]),check:w=>window._waf360.q.push(["check",w])};</script>
<script id="waf360-tag" src="https://tag.waf360.com/tag/tag.js" data-license="YOUR_LICENSE_KEY" async></script>
<!-- Gate your tags on the verdict -->
<script>
window._waf360.check(function(data) {
if (data.block || !data.qs) {
// Flagged traffic: do not load the tag
} else {
// Clean traffic: paste your existing tag snippet here, e.g.
// !function(f,b,e,v,n,t,s){...}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
// fbq('init','YOUR_PIXEL_ID'); fbq('track','PageView');
}
});
</script>
window._waf360.check(callback) is answered as soon as the tag has evaluated the visitor. data.block is true for blocked traffic and data.qs is false when the visitor fails the quality check. See the JavaScript API for the full response fields.check() can be called anywhere on the page, even before the tag script has loaded.onerror and timeout failsafes as shown in Step 1.For tags that accept a custom parameter, such as gtag.js, you can label instead of block: load the tag in both branches and pass the verdict:
<script>
window._waf360.check(function(data) {
var t = (data.block || !data.qs) ? 'bot' : 'clean';
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', { traffic_type: t });
// then load https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX
});
</script>
The traffic_type value flows into the same GA4 data filter described in Step 4. For pixels with no equivalent parameter (Meta, most ad networks), blocking is the right choice, since flagged traffic firing conversion pixels is exactly what pollutes ad-platform learning.
check() response field reference and more conditional loading examples