Tally Forms Event Listener: Track Submissions in GTM
Tally embeds are iframes, so the parent page never sees the submit. Here is the listener that catches it and how to fire conversions from it.

Tally embeds are iframes, so your page never sees the submission directly. This listener catches the message Tally sends out when a form is completed.
How Tally announces a submission
The embedded form posts a message to the parent window containing a JSON string. When someone completes the form, that message contains an event called Tally.FormSubmitted.
The listener parses the message and pushes a clean data layer event when it sees that.
The listener code
This is the Tally listener from DumbData. Worth noting that DumbData credit the original source in the code comment, which is Courtney at Code and Tonic.
The full listener is in the copy block at the bottom of this page, with a copy button. It is reproduced exactly as DumbData publish it, so you can copy it straight from here or from their page.
What it gives you, and what it does not
It pushes tally_form_submit and nothing else. No form ID, no field values.
If you run one Tally form, that is fine. If you run several, you cannot tell them apart from the event alone.
The practical fix is the same as with Elementor. Use GTM’s built in Page Path variable as your form identifier and send it as a GA4 parameter. As long as each form is on its own page, that gives you everything you need without modifying the listener.
Installing it in GTM
Create a Custom HTML tag and paste the listener.
Trigger on the pages with Tally embeds, or All Pages.
Save and Preview.
Creating the trigger
Custom Event trigger, event name tally_form_submit.
Save it as CE Tally Submitted.
Enable the built in Page Path variable under Variables, and use it as your form_id parameter.
Sending it to GA4
Create a GA4 Event tag.
Event name: generate_lead
Trigger: CE Tally Submitted.
Sending conversions to Google Ads
Create a conversion action in Google Ads and note the ID and label.
Add a Google Ads Conversion Tracking tag with those values.
Trigger: CE Tally Submitted.
Meta and everything else
Same trigger, different tag. A Meta Pixel tag with the Lead event, Mixpanel or Piwik PRO from their GTM templates, or a LinkedIn or Microsoft conversion tag.
Testing
Open GTM Preview on the page with the embed.
Complete and submit the form.
Confirm tally_form_submit fires once.
Confirm your conversion tag fired.
Check the submission arrived in Tally.
Common issues
Nothing fires. You may be using a Tally popup or redirect rather than a standard embed. Popups still postMessage, redirects do not.
Cannot separate forms. Expected. Use Page Path as above.
Fires more than once. Two embeds on the page, or the listener installed twice.
Privacy considerations
Never send names, emails, phone numbers or free text into GA4.
Hash anything you send to an ad platform for matching.
Gate the tags behind marketing consent: Consent Mode v2 in GTM
Final thoughts
Tally is a lightweight tool and this is a lightweight listener. Install it, use the page path to tell forms apart, and it takes about ten minutes.
Listener from DumbData.
Need this built properly?
Ten minutes of setup for a tool most people never track at all. I set up and repair tracking for ecommerce stores and lead generation businesses, so the numbers in your ad accounts match the money in your bank account.
Have a look at what I do, or book a call and we can go through your setup together.
<script>
// code source is from Courtney at Code and Tonic (read here https://codeandtonic.com/blog/tally-forms-conversion-tracking)
// Determine if the browser supports "addEventListener" or "attachEvent" for event listening
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
// Assign the event listener function to "eventer" based on what the browser supports
var eventer = window[eventMethod];
// If the browser uses "attachEvent", use "onmessage". Otherwise, use "message"
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Set up the event listener for messages from the child iFrame
eventer(messageEvent,function(e) {
// Check if the event contains "message" or "data" and assign the data to the "data" variable
var key = e.message ? "message" : "data";
var data = e[key];
// If the event data contains "event", we'll proceed further
if(data.includes("event")) {
// Parse the JSON string from the event data
var dataParsed = JSON.parse(data);
// Check if the event is a "Tally.FormSubmitted" event
if(dataParsed.event == "Tally.FormSubmitted"){
// If the Tally form is submitted, push the 'tally_form_submit' event to the Data Layer
// Initialize the Data Layer if it doesn't already exist
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'tally_form_submit'
});
}
}
});
</script>


















