The call runs. dataLayer grows. The console is clean. And GA4 never receives the event. Here is why that happens, and how to prove which half is broken in about five minutes.
This one is nasty because every surface you would normally trust says the setup is fine. Your code executes. No error is thrown. typeof gtag === 'function' is true. The network tab even shows requests going to Google. And the event still does not exist in GA4.
The usual cause is that something replaced gtag with its own function. Analytics plugins do this on purpose, to route measurement through their own settings. WordPress plugins are the common case. The MonsterInsights and ExactMetrics family defines a __gtagTracker wrapper and points window.gtag at it by default. Your call still runs, but it now runs through code you did not write.
gtag.name and gtag.toString(). Genuine gtag is one line: function gtag(){dataLayer.push(arguments);}. Anything that is not that one-line push is a wrapper, whether it is longer or shorter.The wrapper decides where each event goes. Some implementations only forward events that name their destination, and silently discard the ones that do not. That is the single most common version of this bug:
// runs, returns, and goes nowhere
gtag('event', 'generate_lead');
// same call, now routed
gtag('event', 'generate_lead', { send_to: 'G-XXXXXXXXXX' });
Nothing warns you. There is no error, no console message, no failed request. The event simply never becomes a request.
If you send to more than one property, name each one. A single call with one send_to reaches one property, not all of them.
There is a second, meaner variant of the same wrapper. When the plugin is set to exclude the current user from tracking, which by default means logged-in administrators, it replaces gtag with a function that does nothing at all:
window.gtag = function () { return null; };
Now every event is dropped, including the ones that do name a destination. And it only happens to you, because you are the one logged in. You test, see nothing, and start debugging code that works perfectly for every real visitor.
Always confirm in a private window, logged out. This is the single most common reason a developer reports "the event never fires" on a site that is measuring fine.
The second failure looks identical from the outside but has a different cause. This one depends on your consent tool. If it blocks only the remote script, or you use Consent Mode, the small gtag stub exists from the first moment and your calls queue harmlessly. But if it also blocks the inline snippet, and many autoblocking tools do, then for the first seconds of the visit there is no gtag at all.
Now consider the standard guard everyone writes:
if (typeof gtag === 'function') {
gtag('event', 'generate_lead', { send_to: 'G-XXXXXXXXXX' });
}
If the visitor submits your form before consent resolves, that condition is false and the event is dropped on the floor. Quietly. The guard was supposed to make the code safe, and instead it made the loss invisible.
The fix is a queue rather than a guard. Push the event into an array, and flush the array once the real function appears:
var queue = [];
function flush() {
var g = window.gtag || window.__gtagTracker;
if (typeof g !== 'function') return false;
while (queue.length) {
var e = queue.shift();
g('event', e.name, e.params);
}
return true;
}
function track(name, params) {
queue.push({ name: name, params: params });
if (!flush()) watch();
}
// keep retrying for as long as anything is waiting
var timer = null;
function watch() {
if (timer) return;
timer = setInterval(function () {
if (flush()) { clearInterval(timer); timer = null; }
}, 500);
}
watch();
This is where most debugging sessions go wrong. You find the request in the network tab, you see 204 No Content, and you conclude the data landed.
It does not follow. Google's collection endpoint answers 204 to almost anything, including a measurement ID that does not exist. The status code tells you the request was received, not that any property accepted it. Treating 204 as proof is the same mistake as treating a green checkmark in a dashboard as proof.
What actually proves delivery is seeing the event on the receiving side: DebugView, or the realtime report, or a report the next day. Nothing short of that counts.
GA4 does not send every event the instant you call it. It batches. On a page that has just loaded, the first user-triggered event can sit for several seconds before anything leaves the browser.
I have watched this produce a completely false conclusion, my own included: a click event measured with a three-second window looked dead, and the same event measured with a six-second window fired normally. If your test is shorter than the batching delay, you will report a bug that does not exist and go looking for it in the wrong place.
Give any single-event test at least ten seconds before you call it a failure. Then run a control - an event you know works - through the exact same window.
Open a private window with the network tab filtered to collect, and go in this order:
1. gtag.name + gtag.toString() -> wrapper or the real stub?
2. gtag('event','zz_test',{send_to:'G-XXXXXXXXXX'})
... wait 10 s. Request or nothing?
3. same call WITHOUT send_to
... wait 10 s. Request or nothing?
If step 2 produces a request and step 3 does not, the wrapper is dropping unrouted events, and every event in your site that omits send_to is being lost. If neither produces a request, the problem is upstream: consent, a blocked script, or a measurement ID that is not what you think it is.
Every symptom here shares one property: the failure is silent. No error, no warning, no red line anywhere. Silent failures are why tracking rots for months without anyone noticing, and why the platform number and the backend number drift apart until somebody finally compares them.
That is also the reason a tracking setup is not finished when the code is written. It is finished when you have watched one real event travel the whole way and arrive.
Most often something replaced gtag with its own wrapper, and that wrapper only forwards events that name a destination with send_to. Your call executes normally and is then discarded internally, which is why no error appears.
It is the wrapper function some WordPress analytics plugins install so that measurement runs through their settings. When it is present, window.gtag usually points at it rather than at Google's own stub.
No. Google's collection endpoint returns 204 for practically any request, including one sent to a measurement ID that does not exist. Confirm delivery in DebugView or the realtime report instead.
At least ten seconds. GA4 batches events, so the first one after page load can be delayed by several seconds. Short test windows produce false negatives.
The measurement script is blocked until consent, so gtag does not exist yet. Code guarded by a typeof check simply skips the event instead of holding it. Queue events and flush them once the function appears.
Code that runs is not the same as data that arrives. Only the receiving side can tell you which one you have.
Events still not arriving? I do this for a living - tracking audits that trace one real event from the browser all the way to the platform, then fix whatever is eating it. Running in production on my own stores daily.
Before you hire anyone, run the differential test above. If step 2 fires and step 3 does not, you already know the answer and you do not need me. If both are silent, send me your site URL and what you are seeing, and I will tell you which of the usual causes yours is. No charge for that answer.
or email work@rytisbalys.com · every rated job 5.0 · verify on Upwork