Common reasons video-engagement tracking doesn’t fire
- GTM container not published - You imported the container but didn’t click Submit → Publish.
- GTM snippet not installed on your site - The GTM code needs to be in your site’s <head> and <body>.
- YouTube iframe missing
enablejsapi=1- The YouTube IFrame Player API only sends progress events when the iframe URL containsenablejsapi=1. Without it, no play / pause / progress events reach the page - not even from the official Google player. TrackingCoder rewrites the URL automatically, but caching plugins and lazy-load scripts often replace the iframe with the original URL, breaking the param. - Vimeo player.js library not loaded - Vimeo events are exposed via
https://player.vimeo.com/api/player.js. If that script is blocked, deferred, or never reaches the page, no events fire. Check the Network tab for the file. - HTML5
<video>events missing - Native HTML5 video events (play,pause,ended,timeupdate) only fire on the<video>element directly. If your tracking script attaches to a wrapper or button instead, no events fire. - Lazy-loaded video iframe - Pages that defer video iframes until scroll-into-view (Divi, Elementor, LiteSpeed lazy-load) replace the original iframe AFTER the page loads. A listener attached on DOM Ready won’t see the late-arriving iframe. The TrackingCoder snippet uses a MutationObserver to handle this, but a hostile lazy-load script can re-replace the iframe and strip the param a second time.
- Milestone never reached - If you only enabled the 100% milestone but the video is 30 minutes long, almost no users will reach it. Milestones at 0% (start), 25%, 50%, 75% give a more useful funnel and fire on every play.
- Divi video module re-renders the iframe - Divi’s
.et_pb_videomodule replaces the YouTube iframe after page load when the user clicks the play button overlay. Anyenablejsapi=1param TrackingCoder added gets stripped during the replacement, so progress events stop. The snippet retries up to 3 times to re-add it. - LiteSpeed Cache lazy-loads video iframes - LiteSpeed defers iframes via
data-deferred-srcuntil scroll-into-view, which means the iframe with ourenablejsapi=1param doesn’t exist on initial DOM Ready. The MutationObserver in our snippet catches this - but only if LiteSpeed isn’t aggressively combining/minifying the script. - Elementor video widget wraps in a custom overlay - Elementor’s video widget adds a play-button overlay (
.elementor-custom-embed-play) that intercepts the first click. Our snippet listens for clicks on these overlays and fires the start (0%) milestone manually since the YouTube API may not see the click pass through.
Step-by-step debugging checklist
- Verify GTM is installed - Visit your site, right-click → View Page Source, search for “GTM-”. If you don’t see it, GTM isn’t installed.
- Check GTM Preview mode - In GTM, click Preview. Navigate your site and trigger the event. Look for your tag in the Tags Fired section.
- Check the trigger - If the tag shows as “Not Fired”, click on it to see which trigger conditions aren’t met.
- Identify the video type first - Right-click the video and inspect. Is it
<video>(HTML5),<iframe src="youtube.com/...">(YouTube), or<iframe src="player.vimeo.com/...">(Vimeo)? The fix is different for each. - For YouTube: check
enablejsapi=1- With GTM Preview open, inspect the iframe element on the live page. Itssrcattribute should containenablejsapi=1. If it doesn’t, a lazy-load or caching script is overwriting it after our snippet ran. You’ll need to either disable the lazy-load for that video or move the snippet to fire AFTER the iframe is final. - For HTML5: open the console and listen manually - Run
document.querySelector('video').addEventListener('play', () => console.log('play')). Click play. If “play” doesn’t log, the video is in an iframe (different origin) and HTML5 events can’t cross the boundary. - Check GTM Preview while playing - Play the video, scrub through it, let it end. You should see
tc_video_engagementevents in the GTM event timeline at each milestone you enabled. If you see nothing, the snippet didn’t attach to the player.
WordPress-specific issues
The two most common breakers in WordPress are page builders that re-render iframes (Divi’s video module, Elementor’s video widget) and aggressive caching plugins (LiteSpeed Cache, WP Rocket) that defer or combine JavaScript. The TrackingCoder snippet handles both with a MutationObserver and a 3-attempt retry on the YouTube enablejsapi=1 param, but if events still don’t fire after import, the cache is the most likely culprit.
Try clearing your caching plugin’s cache, then visiting the page in an incognito window with GTM Preview. If tc_video_engagement events appear in incognito but not in your normal browser, your browser is serving a stale cached version of the page that pre-dates the GTM container import.
If you’re using Elementor, also check whether the video widget is set to “Self-hosted” mode. Self-hosted videos use HTML5 <video> elements directly and don’t need the iframe-based logic - but Elementor wraps them in a play-button overlay that needs special handling.
How to verify it’s working
- Open GTM Preview/Debug mode
- Navigate to your site
- Trigger the event (play, scrub through, and finish a video)
- Check the GTM debug panel - your tag should appear under “Tags Fired”
- Check GA4 DebugView (GA4 → Admin → DebugView) to confirm the event arrived
Still didn’t work?
Our team can help. Describe what’s happening and we’ll get back to you within 24 hours.