Blog

Tally conversion tracking: 3 ways to do it

Learn three different ways to set up conversion tracking for Tally forms, and how to work out which one is right for you.

Tally conversion tracking: 3 ways to do it

If you're running ads and sending people to a page on your website with a Tally form on it, then you need to set up conversion tracking.

It tells you which campaigns, ads and keywords are actually producing leads, and it gives the ad platforms (Google, Meta, etc) the data they need to learn who converts and who doesn't, so they can put your ads in front of more of the right people.

The problem is that it's quite difficult to do. When you embed a Tally form on your site, it loads inside an iFrame served from Tally's own domain, so the form isn't actually part of your page. That means a lot of the standard conversion tracking advice you'll find online simply doesn't work with it.

So in this article, we'll walk you through three ways to set up conversion tracking for Tally forms, with full instructions and some resources (a code snippet, a Google Tag Manager recipe, etc) to make each one easier.

Method 1: Send a GA4 event via Google Tag Manager

Good for: Sending form submission events to Google Analytics 4, for free.

If all you need to do is send a conversion event to Google Analytics whenever someone submits a Tally form, this is the method to use. Google Analytics doesn't need to know who filled in the form (unlike Google Ads or Meta Ads), only that somebody did.

Tally does have a built-in Google Analytics integration, but it's only available on Tally Pro, which costs $24 a month (or about $20 a month if you pay annually). If you're not on Pro, or you'd rather keep all your tracking in one place, the free way to do it is with Google Tag Manager.

The problem is that Google Tag Manager's built-in form submission trigger can't see a Tally form, because the form lives inside an iFrame. So what you need instead is some custom code that listens for the message Tally sends your page when a form is submitted and pushes an event into the dataLayer (and then you need to build your Triggers and Tags in GTM from that).

Sound difficult? No need to worry, because we've packaged it all into a free Google Tag Manager recipe you can download and use (a recipe is basically a bundle of pre-built assets you import straight into your account). Here's how to use it:

Step 1: Download the Tally Conversion Tracking GTM Recipe

Start by grabbing the Tally GTM recipe from this page. Inside, you'll find:

  • A Custom HTML Tag that listens for the message Tally sends your page once a form has been successfully submitted, and pushes a tally_form_submitted event into Google Tag Manager.
  • Two Variables holding the ID and the name of the form that was submitted, which are handy if you want to fire different tags for different forms (i.e. you want to send a conversion when someone submits your Request A Quote form, but not your Newsletter Signup form).
  • A Trigger tied to the tally_form_submitted event which you use to fire whatever conversion tags you build (a GA4 event, a Google Ads conversion, and so on).

Step 2: Import the GTM recipe into your account

With the file downloaded, the next job is getting it into your Google Tag Manager account. To do that, open Google Tag Manager, go to Admin, choose Import Container, select the file, and pick the Merge option so nothing already in your container gets overwritten. The listener, both variables and the trigger are all created for you.

Step 3: Build a GA4 event tag

The recipe gives you the trigger, but you still need to set up the Tag to send the conversion event to GA4. To do that, create a GA4 event tag and set its firing trigger to the one the recipe installed (called CE - Tally Form Submitted).

Create a GA4 event tag and fire it on the trigger that comes with the Tally recipe

Step 4: Test it, then mark it as a Key Event in GA4

With everything wired up, the last job is proving it works. Submit a test entry on one of your Tally forms, open GA4's Realtime report, and check the event turns up in the Recent Events list.

Once it's arriving, go to Admin, then Events, find the event in the list, and toggle it on as a Key Event. This is the step people forget, and it's what tells GA4 to treat the event as a conversion in your acquisition and funnel reports (rather than leaving it sitting there as an ordinary event).

Pros and cons

Pros:

  • It's free. Google Tag Manager costs nothing, neither does the recipe, and you don't need to be on Tally Pro.
  • It works with all Tally embed types, including Standard, Popup, and Full Page Embed.
  • It only fires once Tally has accepted the submission. If someone leaves a required question blank or types their email in the wrong format, Tally rejects the form submission. If that happens, the recipe does not fire.
  • Most of the fiddly work is already done. The listener, trigger and variables come pre-built, so the GA4 event tag is the only piece you build by hand.

Cons:

  • There's still setup work. The recipe takes care of the hardest part (detecting the form submission), but building the GA4 tag and checking it all works is on you. And if you get stuck, there's nobody to ask (Google doesn't provide support for Google Tag Manager).
  • Every Tally form on your site pushes the same event. The recipe does pass along the form's ID and name, but unless you go and build a separate trigger for each form, a submission of your Contact Support form sends the same event to GA4 as a submission of your Quote Request form (with no way to tell them apart). That's fine if you only have one form, but a real problem if you have several.
  • The event is sent to Google Analytics from the visitor's browser, so ad blockers and privacy-focused browsers like Safari can stop it from ever reaching GA4. Industry studies put the loss at roughly 30% of conversions.
  • It stops at Google Analytics. Nothing here reaches Google Ads, Meta Ads, or any other ad platform (that's what Methods 2 and 3 are for).

Method 2: Use Google Tag Manager for Enhanced Conversions

Good for: Sending Enhanced Conversions to Google Ads.

Counting the number of form submissions (the way Method 1 does) is enough for Google Analytics, but it won't get you very far in Google Ads.

Google Ads wants to match each conversion back to the real person who clicked your ad, and to do that it needs details like their name, email and phone number sent along with the conversion. Google calls this Enhanced Conversions.

Normally, an iFrame form would rule this out completely, because nothing on your page can read what someone typed into it. But Tally works differently. When a Tally form is successfully submitted, it sends a message to your page that includes every answer the visitor gave, and you can grab the name, email and phone out of that message. Here's how to set it up with Google Tag Manager.

Step 1: Set up a Custom HTML Tag with the form listening code

In GTM, create a new Custom HTML Tag, paste in the snippet below, and set it to fire on the All Pages trigger, with the tag firing option set to Once per event.

<!-- Converly GTM Recipe — Tally submission listener (Method 2: with PII) This is the RICHER snippet used by the blog post's "Method 2: GTM with Enhanced-Conversions data". It captures the lead's email, first name, last name and phone alongside the submission event, which is what turns on Enhanced Conversions in Google Ads and lifts Meta's Event Match Quality. It shares its detection core with the event-only recipe snippet (blog-snippet.html) line for line: the same Tally.FormSubmitted message, the same origin check (https only, host anchored on tally.so), the same self-post and no-source rejection, the same payload-shape check, and the same event name, so the two can never drift on WHEN they fire or WHICH form fired. The only difference is that this one also reads the answers out of that message and pushes them. THE TALLY QUIRK: a Tally form always lives inside a cross-origin <iframe> on tally.so, so no script on your page can read its fields. That would normally rule out capturing the lead's details from the browser. Tally is the exception: once ITS server has accepted the submission, the iframe posts a Tally.FormSubmitted message to your page, and that message carries every answer ({ title, type, answer: { value } } per question). So there is nothing to snapshot at submit time and nothing to hold. The answers arrive with the success signal itself, and a submission Tally rejects never produces the message at all. Source: ported from cdn/v1/modules/tally.js (the real Converly detection module): the fire path plus its field-reading and identification pass (question title and Tally's question type, sensitive and hidden questions skipped). No Converly internals, no API keys. This runs standalone inside a GTM Custom HTML tag. Caveat, the same one the event-only recipe carries: this snippet validates the message's origin and shape, but unlike the production module it does NOT bind the message to a specific tally.so <iframe> your page renders. A script that is already running inside some tally.so page could therefore post a well-formed message. Skip this snippet if that risk matters for your site. Last verified: see HOW-IT-WORKS.md → "Live-verified" --> <!-- STEP 1 — Custom HTML tag. Create one GTM tag with this content, firing trigger: All Pages, tag firing option: Once per event. --> <script> (function () { 'use strict'; if (window.__converlyTallyMethod2Installed) return; window.__converlyTallyMethod2Installed = true; window.dataLayer = window.dataLayer || []; // ============================================================ // 1. Sensitive-field guard // ============================================================ // Passwords, card numbers, CVV / CVC, one-time codes, SSN and PIN are never // lead identifiers. They must never reach the dataLayer. Tally's message // carries no input name or autocomplete attribute, so two signals are // checked, both BEFORE the answer is read: Tally's own question type, and // the question title. // Question types that are never read. HIDDEN_FIELDS are values your page // passed into the form (UTMs, attribution), not something the lead typed. var SKIP_TYPES = { HIDDEN_FIELDS: true, PAYMENT: true, FILE_UPLOAD: true, SIGNATURE: true, INPUT_PASSWORD: true, PASSWORD: true }; var SENSITIVE_NAME_SUBSTRINGS = [ 'password', 'passwd', 'passcode', 'cardnum', 'ccnumber', 'creditcard', 'onetimecode', 'securitycode', 'socialsecurity' ]; var SENSITIVE_NAME_TOKENS = { otp: true, cvv: true, cvc: true, ssn: true, pwd: true, pin: true }; function normaliseKey(value) { return String(value || '').toLowerCase().replace(/[^a-z0-9]/g, ''); } function isSensitiveName(name) { if (!name) return false; var lower = String(name).toLowerCase(); var norm = normaliseKey(lower); for (var i = 0; i < SENSITIVE_NAME_SUBSTRINGS.length; i++) { if (norm.indexOf(SENSITIVE_NAME_SUBSTRINGS[i]) !== -1) return true; } var parts = lower.split(/[^a-z0-9]+/); for (var p = 0; p < parts.length; p++) { if (SENSITIVE_NAME_TOKENS[parts[p]] === true) return true; } return false; } // ============================================================ // 2. Read the answers out of Tally's message // ============================================================ // Real Tally payloads name each question by `title` and put the answer at // `answer.value` (captured live). `label` / `key` / `value` are kept as // fallbacks for any older or variant shape, exactly as the module does. function extractFields(payload) { var fields = []; if (!payload || !payload.fields || !payload.fields.length) return fields; for (var i = 0; i < payload.fields.length; i++) { var f = payload.fields[i]; if (!f) continue; var tallyType = String(f.type || '').toUpperCase(); if (SKIP_TYPES[tallyType] === true) continue; var title = f.title || f.label || f.key || ''; if (!title) continue; // A plain text question TITLED like a secret ("One-time code", "Card // number") is skipped before its answer is touched. if (isSensitiveName(title)) continue; var value = (f.answer && f.answer.value !== undefined && f.answer.value !== null) ? f.answer.value : f.value; // Checkbox / multi-select answers arrive as arrays. if (Array.isArray(value)) value = value.join(', '); if (value === undefined || value === null) value = ''; // Map Tally's question type onto an input-like type for scoring. var type = 'text'; if (tallyType === 'INPUT_EMAIL') type = 'email'; else if (tallyType === 'INPUT_PHONE_NUMBER') type = 'tel'; fields.push({ title: title, type: type, value: String(value) }); } return fields; } // ============================================================ // 3. Identify which answers are email / phone / first / last name // ============================================================ // Layered signals, strongest wins: // 100 Tally's question type (Email block, Phone number block) // 50 the question title ("First name", "Your email", "Surname", ...) // Tally sends no field names, so the title is the only text signal. A single // "Name" / "Full name" question is split into first and last name. function scoreField(field) { var scores = { email: 0, phone: 0, firstName: 0, lastName: 0, fullName: 0 }; var type = field.type; var key = normaliseKey(field.title); if (type === 'email') scores.email = 100; if (type === 'tel') scores.phone = 100; if (key.indexOf('email') !== -1) scores.email = Math.max(scores.email, 50); if (key.indexOf('phone') !== -1 || key.indexOf('mobile') !== -1 || key === 'tel' || key.indexOf('telephone') !== -1) { scores.phone = Math.max(scores.phone, 50); } if (key === 'firstname' || key === 'first' || key === 'fname' || key === 'givenname' || key.indexOf('firstname') !== -1 || key.indexOf('givenname') !== -1) { scores.firstName = Math.max(scores.firstName, 50); } if (key === 'lastname' || key === 'last' || key === 'lname' || key === 'familyname' || key.indexOf('lastname') !== -1 || key.indexOf('surname') !== -1 || key.indexOf('familyname') !== -1) { scores.lastName = Math.max(scores.lastName, 50); } if (scores.firstName === 0 && scores.lastName === 0 && (key === 'name' || key === 'fullname' || key === 'yourname')) { scores.fullName = Math.max(scores.fullName, 50); } return scores; } function identify(fields) { var best = { email: { value: undefined, score: 0 }, phone: { value: undefined, score: 0 }, firstName: { value: undefined, score: 0 }, lastName: { value: undefined, score: 0 }, fullName: { value: undefined, score: 0 } }; for (var i = 0; i < fields.length; i++) { var field = fields[i]; var trimmed = String(field.value || '').trim(); if (!trimmed) continue; var scores = scoreField(field); for (var category in scores) { if (scores[category] > best[category].score) { best[category] = { value: trimmed, score: scores[category] }; } } } var result = {}; if (best.email.value) result.email = best.email.value; if (best.phone.value) result.phone = best.phone.value; if (best.firstName.value) result.firstName = best.firstName.value; if (best.lastName.value) result.lastName = best.lastName.value; if (best.fullName.value && !result.firstName && !result.lastName) { var parts = best.fullName.value.split(/\s+/); result.firstName = parts[0]; if (parts.length > 1) result.lastName = parts.slice(1).join(' '); } return result; } function buildUser(payload) { var u = identify(extractFields(payload)); var out = {}; if (u.email) out.email = u.email; if (u.firstName) out.first_name = u.firstName; if (u.lastName) out.last_name = u.lastName; if (u.phone) out.phone = u.phone; return out; } // ============================================================ // 4. Detection core (identical to blog-snippet.html) // ============================================================ function fireConversion(formId, formName, user) { var push = { event: 'tally_form_submitted', form_id: formId || '', form_name: formName || '' }; if (user) { if (user.email) push.email = user.email; if (user.first_name) push.first_name = user.first_name; if (user.last_name) push.last_name = user.last_name; if (user.phone) push.phone = user.phone; } window.dataLayer.push(push); } function parseMessageData(data) { if (!data) return null; if (typeof data === 'string') { try { return JSON.parse(data); } catch (e) { return null; } } if (typeof data === 'object') return data; return null; } // HTTPS-only, host-anchored so "evil-tally.so.attacker.io" (substring) // and "http://tally.so" (scheme downgrade) both fail. var TALLY_ORIGIN_PATTERN = /(^|\.)tally\.so$/; function isTrustedOrigin(event) { if (!event) return false; // Reject self-posted messages: Tally never posts from our own window. if (event.source === window) return false; // Reject messages with no source — a real cross-origin iframe message // always carries a source WindowProxy. if (!event.source) return false; if (!event.origin || typeof event.origin !== 'string') return false; var hostMatch = /^https:\/\/([^/]+)/.exec(event.origin); if (!hostMatch) return false; return TALLY_ORIGIN_PATTERN.test(hostMatch[1]); } function handleMessage(event) { try { if (!event || !event.data) return; var msg = parseMessageData(event.data); if (!msg) return; var eventName = msg.event || msg.type; if (eventName !== 'Tally.FormSubmitted') return; if (!isTrustedOrigin(event)) return; // Payload-schema check: a well-formed Tally.FormSubmitted carries a // payload object whose `fields` is an array. Without this a malformed // (but origin-trusted) message could still fire a phantom conversion. var payload = msg.payload || msg.data || msg; if (!payload || typeof payload !== 'object' || !Array.isArray(payload.fields)) return; // Only now, with the message trusted, are the answers read. fireConversion(payload.formId, payload.formName, buildUser(payload)); } catch (e) { // Never throw back into the page's own message handling. } } if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('message', handleMessage, false); } })(); </script> <!-- STEP 2 — Data Layer Variables. Create one for each value you want to use: Name: DLV - Tally Form ID | Data Layer Variable Name: form_id Name: DLV - Email | Data Layer Variable Name: email Name: DLV - First Name | Data Layer Variable Name: first_name Name: DLV - Last Name | Data Layer Variable Name: last_name Name: DLV - Phone | Data Layer Variable Name: phone (form_name is pushed too, as in the event-only recipe, if you want it.) --> <!-- STEP 3 — Trigger. Create one Custom Event trigger: Name: Tally Submission Event name: tally_form_submitted (does not match regex) --> <!-- STEP 4 — attach the trigger from Step 3 to your destination tag (Google Ads Enhanced Conversions, Meta Pixel, etc.) and map the Data Layer Variables from Step 2 into that tag's user-data / customer-data fields. That mapping is destination-specific and isn't included in this recipe. -->

The above code does a bunch of things:

  • Waits for Tally to accept the submission. Tally only sends its "form submitted" message to your page after its own servers have accepted the entry. If a visitor misses a required question or types an invalid email, Tally blocks the form submission and the success message is never sent, so no conversion is fired.
  • Checks the message really came from Tally. Any script running on your page can send a ‘form submitted’ message that looks like Tally's. So before trusting it, the snippet confirms it came from a secure Tally web address, ignores anything your own page sends to itself, and checks it has the layout of a real submission.
  • Skips hidden, payment and sensitive questions. Hidden fields (the UTMs and other values your page passes into the form) are ignored, along with payment, signature, file upload and password questions, and anything titled like a card number, CVV or PIN. They're dropped before the snippet ever looks at the answer.
  • Grabs the lead's details (name, email, phone). Tally's Email and Phone number questions are recognised by their question type (regardless of what title you gave the question), so that's easy. Other fields, like "First name" or "Surname", are found by looking at the question title. If you have a single "Name" question, then the code automatically splits it into first and last name (which is what Google Ads requires).
  • Sends all the data to the dataLayer. Finally, it pushes a tally_form_submitted event to the dataLayer with the form's ID and name, plus the lead's email, first name, last name and phone (if your form actually asks for them).

This snippet has been extensively tested and deals with a number of issues that other scripts on the internet don't, including:

Issue 1: A Tally form is invisible to the usual tracking methods. Google Tag Manager has a built-in Form Submission trigger, but because the form lives inside an iFrame on your page, that trigger can't see it. So you'd set it all up, your conversion count would sit at zero, and nothing would tell you why. This snippet reads the answers from the message Tally sends your page instead, which is the one place they're available.

Issue 2: Most scripts online don't check who sent the message. A lot of the Tally tracking snippets floating around fire on any message with the right event name. Any other script that loads in the visitor's browser (a browser extension, a third-party widget, another ad) can send the same message, and each one would be recorded as a conversion (complete with whatever name and email it made up). This snippet checks the sender and the contents before it trusts anything.

Issue 3: Hidden fields travel alongside the real answers. If you pass UTMs or other values into your Tally form using hidden fields, those get included alongside the lead's details in the ‘Form Submission Successful’ message. If one of them contains a word like ‘email’ then a script that doesn't tell them apart could send Google Ads the wrong email address, which means the conversion never matches back to the person who clicked your ad. This snippet skips hidden fields completely, so only the data the lead actually typed into the form gets used.

As you can probably tell from the above, this isn't a snippet we threw together for this article. It's the same logic our own conversion tracking software uses to detect Tally submissions and send them to ad platforms and analytics tools, and it's been tested against a real Tally form many times, so you can use it with confidence.

Step 2: Create Data Layer Variables for the captured data

The snippet above sends the lead's details into the dataLayer, but Google Tag Manager won't use them until you tell it how to pull each one out. That's what a Data Layer Variable does. You'll create one each for the email, first name, last name and phone.

To do this, go to the Variables section in GTM, click New under User-Defined Variables, choose Data Layer Variable as the type, and give it a name.

Create Data Layer Variables for email, first name, last name and phone

Here are the four you need.

Variable 1:

  • Name: Email
  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: email

Variable 2:

  • Name: First Name
  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: first_name

Variable 3:

  • Name: Last Name
  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: last_name

Variable 4:

  • Name: Phone
  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: phone

Step 3: Create a Custom Event trigger

With the lead's details being sent to the dataLayer and variables set up to pull them out, the next step is telling Google Tag Manager when to use them. That's the job of a Trigger. It watches for the event the snippet pushes into the dataLayer when a Tally form is accepted (called tally_form_submitted), and tells your conversion tags to fire when it arrives.

To set it up, go to the Triggers section, click New, choose the Custom Event type, name it Tally Submission, and set the event name to tally_form_submitted (it has to match what the code pushes exactly).

Create a Custom Event trigger in Google Tag Manager

Step 4: Turn on Enhanced Conversions in Google Ads

Before any of this data can reach Google Ads, Google Ads has to agree to accept it. To set that up, head to your Google Ads account, go to Goals, then Conversions, then Settings, and turn on Enhanced Conversions.

Turn on Enhanced Conversions in your Google Ads settings

When it asks how you want to implement it, choose Google Tag Manager. That gives Google Ads permission to receive and use the customer data you're about to send.

Step 5: Create your Google Ads conversion tag and map the data

Now it's time to put all the pieces together. This tag is what actually sends the conversion. It fires on the Trigger you created in Step 3 and passes through the data from the Variables you set up in Step 2.

To build it, jump back into GTM, go to Tags, select New, choose Google Ads User Provided Data Event as the tag type, and map the variables from Step 2 to the fields it asks for.

Map your Data Layer Variables in the Google Ads User Provided Data tag

Then set it to fire on the Custom Event trigger you created in Step 3.

Set the Google Ads tag to fire on the Tally Submission trigger

Once that's done, publish your GTM container.

With all of this complete, you should now have a Tag that listens for Tally submissions and sends an event to the dataLayer, some Variables that pull the lead's details out of it, a Trigger that watches for the event, and another Tag that sends the conversion to Google Ads when the Trigger fires.

To check it's working, open GTM Preview mode, submit the Tally form on your site, and confirm the tally_form_submitted event arrives with the email and name filled in.

Pros and cons

Pros:

  • It's free, and you may already have Google Tag Manager installed on your site, so that's a start.
  • Unlike Method 1, it captures the name, email and phone and sends them to Google Ads (the minimum you need for Enhanced Conversions).
  • It gets the lead's details out of the iFramed form, which is normally impossible, and only fires once Tally has accepted the submission.

Cons:

  • There are a lot of moving parts. Between the Custom HTML tag, four Variables, the Trigger, the Google Ads settings and the conversion Tag itself, there's plenty to get wrong. And when something does go wrong, nothing tells you where or why. It just doesn't work, and it's up to you to figure out what happened (Google doesn't provide support for Tag Manager, so you are literally on your own).
  • The conversion is still sent from the visitor's browser, so ad blockers and privacy browsers can stop it from firing. Expect to lose 30% or more of your conversions this way.
  • It still misses the data Google Ads values most. The name, email and phone are useful, but millions of people share the same name, and plenty of people have more than one email address (so the one they typed into your form may not be the one they were logged in to Google with when they clicked your ad). What really ties a conversion back to the original ad click is the Google Click ID, and this method doesn't capture it.

That last point is what pushes a lot of people to Method 3.

Method 3: Use Converly

Best for: Sending proper, server-side conversions to Google Ads, Meta Ads, ChatGPT Ads, LinkedIn Ads, and more, without having to do complicated configuration in Google Tag Manager.

Converly is a conversion tracking tool built for one job, which is sending server-side conversions to Google Ads, Meta Ads, LinkedIn Ads and more whenever someone submits a Tally form on your website.

Converly flow builder connecting Tally Forms to Google Analytics, Google Ads, and Meta Ads

The screenshot above is basically the whole setup process. You simply choose a trigger (in this case, a Tally form being submitted), add one or more actions (such as sending a conversion to Google Ads or Meta Ads), and you're done. There's no code to write and nothing to wire up by hand in Tag Manager, and the fact that Tally loads your form inside an iFrame doesn't matter, because Converly handles that for you automatically.

Once that's configured, you add the snippet of code Converly gives you to your site and it takes care of the rest. It watches for Tally form submissions, extracts the name, email and phone, and sends them to Converly's own servers. From there they're passed on to Google Ads, Meta Ads and any other destination you've connected. Because that last step happens through Converly's servers rather than in the visitor's browser, ad blockers and privacy browsers like Safari can't get in the way, so the ad platforms end up with far more accurate conversion data.

And if you advertise on Google, Meta, ChatGPT or similar platforms, this next part is what really makes the difference. As well as the name, email and phone, Converly captures the click IDs (i.e. GCLID, fbclid, etc), the browser cookies set by the ad platforms, the visitor's IP address and their user agent, and sends the lot with every conversion. Sending all of this extra data to your ad platforms is what lifts your match quality from mediocre to strong (Meta's Event Match Quality score, for example, usually rises from around 3 to somewhere between 9 and 10).

There's also a full conversion log built in, showing every lead who submitted a Tally form, exactly what data Converly captured, what got forwarded to each platform, and whether it made it through successfully.

Converly's conversion record showing the marketing attribution, click IDs, and browser data captured with each lead

Pros and cons

Pros:

  • Setup is genuinely quick and simple. Pick a trigger, pick your actions, and you're done. There's no code to write or maintain, nothing complicated to configure, and no need to work around Tally's iFrame yourself.
  • Sends click IDs, cookies, IP address and user agent with every conversion, which gives match quality in the ad platforms a real boost (Meta's Event Match Quality score typically goes from a 3 or 4 up to a 9 or 10 with Converly, for example).
  • Sends conversions server-side, so ad blockers and privacy browsers can't stop them. Expect around 30% more recorded conversions than you'd get from a Google Tag Manager setup.
  • Supports conditional logic, so you decide which forms actually count as a conversion (like sending a conversion when someone submits your Quote Request form, but not your Contact Support form).
  • Lets you connect Google Ads, Meta Ads, etc directly inside Converly and just pick which conversion should fire. No digging around for account IDs or conversion IDs.
  • Comes with a dedicated MCP and CLI, so you can connect an AI tool like Claude or ChatGPT, tell it to ‘Set up conversion tracking in Google Ads and Meta Ads when someone submits my Tally form’, and it will do the whole thing for you.
  • Includes a full conversion log covering every lead who submitted a Tally form, what data was captured, whether it reached your ad platforms, and more.
  • Backed by a real support team who'll help you get set up and fix any problems. Good luck getting that kind of help from Google when your GTM setup breaks.

Cons:

  • It costs money once the free trial is over. Plans start at $19 a month.

What not to do

We've now covered the three best ways to set up conversion tracking for Tally forms, and when each one makes sense. But it's just as useful to know which approaches to avoid, because some of the most common ways people try to track Tally submissions don't work very well. Here are the ones to steer clear of:

Tracking thank-you page visits

This is still the most popular DIY approach. Tally makes it easy to redirect people to a page of your choosing (like a thank you page) once they complete a form, so it feels like the natural thing to track. In practice it causes more problems than it fixes:

It counts people who never submitted anything. A thank-you page is just a web page, so people can land on it without ever submitting your form. They might bookmark it (particularly if it contains useful information), get sent to it from their browser history or autocomplete, receive the link from a colleague, or find it in Google if it ever gets indexed. Every one of those visits is counted as a conversion, even though nobody submitted a form.

It can count the same lead more than once. If someone refreshes the thank-you page, that's a second conversion. If they leave but hit the back button and land on it again, that's another conversion. One lead can end up being counted several times, which inflates your numbers and gets Google and Meta optimising your ads against conversion data that isn't true.

You would get terrible match rates. A thank you page doesn't know who submitted the form, so it can't send through the name, email, phone, GCLID, fbclid, etc. that ad platforms need to match everything up. So even though conversions reach these ad platforms, they're often not counted because they're missing the data needed to tie each one to a real person and the original ad they clicked.

Relying on the default form submit event in Google Tag Manager

When people set up tracking in Google Tag Manager, the natural first move is to use GTM's built-in Form Submission trigger, which listens for the browser's native form submit event.

But with Tally Forms, that event never fires. Tally iFrames the form into your page, and the submission happens inside Tally's iFrame where Google Tag Manager can't see it. So if you used the built-in Form Submission trigger, you'd set the whole thing up, assume it was working, and your conversion count in Google Ads would sit at zero with nothing to tell you why.

What you need to listen for is the message Tally sends your page once the form has been successfully submitted, which is what Methods 1 through 3 above all do.

Using GA4's automatic form tracking (Enhanced Measurement)

GA4 has a built-in Enhanced Measurement feature that promises to track form submissions automatically, with no setup required.

The trouble is that it only works if the form uses the browser's native form submit event, which means it runs into the same wall as GTM's default trigger. A Tally form submits inside its own iFrame, so Enhanced Measurement has nothing to detect and your Tally submissions won't be recorded as form submissions.

If you want Tally submissions in GA4 reliably, use the free recipe from Method 1 above, or use Converly.

Firing the conversion on the submit button click

Some conversion tracking setups fire on the Submit button click itself, usually with a click trigger in GTM. With Tally, that doesn't even get off the ground. The button is inside Tally's iFrame, so GTM's click trigger on your page never sees it being clicked.

As we said above, you need to use Tally's real confirmation message that the form was submitted successfully, not a stand-in for it like a button click.

So which method should I use?

Here's how the three ways to track Tally conversions compare.

MethodBest forSends conversions toData sent
Method 1. GA4 event built in Google Tag ManagerSending submissions to Google Analytics (use our free GTM recipe to make it easier)Google Analytics 4The submission event only
Method 2. Enhanced Conversions through Google Tag ManagerSending to Google Ads, if fiddly configuration doesn't put you offGoogle AdsName, email and phone
Method 3. ConverlySending to any ad platform, with the least setup workGoogle Ads, Meta Ads, LinkedIn Ads, ChatGPT Ads, GA4 and moreName, email and phone, plus the GCLID, the Meta click ID, IP address and user agent

Wrap up

Conversion tracking rarely gets much attention, but it's one of the biggest factors in how well your advertising actually performs.

If you take the time to set it up properly, the ad platforms' algorithms get the data they need to learn what your ideal customer looks like, and can put your ads in front of more people like them. Ultimately, this means you get more leads at a lower cost per lead.

Google and Meta have even published figures on how much difference this makes. Google says businesses that switch to proper server-side conversion tracking see their conversions climb by around 19% on average, and Meta reports a 23% lift from making the same move.

With Converly, you get all the benefits of server-side tracking with none of the hard parts. You simply choose your trigger (someone submitting a Tally form), pick which platforms you want the conversions sent to, and you're done (and if you connect an AI tool like Claude or ChatGPT, it's even easier. Just connect it, ask it to 'set up Tally conversion tracking', and it handles everything for you through our MCP or CLI).

It's free to get started (no credit card needed for the trial), and most people have it fully up and running in well under 10 minutes. Start your 14-day free trial today.

About the author

Aaron Beashel
Aaron Beashel

Aaron is the founder of Converly. With over 15 years of experience in digital marketing and SaaS, he's passionate about helping businesses track and optimise their ad conversions.

Start your free trial

Easily send conversions to your ad platforms and analytics tools.
No code required.