What Converly treats as a conversion, the five categories a conversion can fall into, and what each category means for the data you end up with.

The rule

Worth knowing

A conversion is recorded only when the tool confirms the submission succeeded. Converly never tracks proxies for a conversion (like clicking the submit button or visiting a thank you page) and it never tracks rejected form submissions (like those that get rejected because the user missed a required field, or entered their email address in the wrong format, or spam submissions).

Approaches to detection

There are many different ways to detect a successful form submission (or meeting being booked or chat being started), and it really depends on the tool you are using. Some form tools send a postMessage event when a form is successfully submitted, others you need to watch for a visible element on the page. Some give you the lead's name, email, phone, etc with the success event, others you have to call the API or set up a Webhook.

Generally speaking though, they all fall into one of 5 categories, and the category determines what Converly can capture and how it captures it.

CategoryHow it works
Category 1: Read from the pageThe form is part of the HTML of the page, so the lead's details can be grabbed from the form when it is successfully submitted. Examples include Gravity Forms, Webflow forms, Elementor forms and more.
Category 2: Sent by the toolThe form is loaded on the page but isn't part of it (usually it's loaded inside an iFrame). However, its success message carries the lead's name, email, phone, etc. so they can be captured from there. Examples include HubSpot Forms, Tally Forms and others.
Category 3: Fetched from the toolThe form is loaded on the page but isn't part of it (usually it's loaded inside an iFrame). It sends a success message but doesn't include the lead's name, email, phone etc so Converly listens for the success message then fetches the lead's details via an API call. Examples include Typeform, Calendly and Acuity Scheduling.
Category 4: Reported server to serverThe conversion happens somewhere where Converly's code cannot be installed, so the platform sends it to us directly (usually via Webhooks or a native integration). Examples include Stripe, Recurly and Teachable.
Category 5: Custom conversionsConverly has an API which allows you to send custom conversion actions. Examples include a SaaS tool sending a conversion when someone purchases a subscription, or a custom-coded website sending a conversion when their custom-built 'Request A Quote' tool is used.

Category 1: Read from the page

Tools in this category render the form in the HTML of the page, so both detecting the form was successfully submitted and extracting the lead's name, email, phone, etc. can be done via the Converly JavaScript code you place on your site.

How data is captured

Even within this category, there is no single way to detect a successful form submission (or a meeting being booked or a chat being started). This means detection has to be implemented on a per tool basis.

Generally speaking though, there are 4 different ways this happens:

PatternConditionWhat Converly does
Success eventfields readableThe tool emits an event on success and the submitted values are still present. Values are read at that point.
Success eventfields clearedThe tool clears the form before announcing success. Values are captured as they are entered and held until success is confirmed.
Framework eventproprietaryThe tool uses its own internal messaging rather than standard browser events. Detection subscribes to that framework directly.
Dual embedhosted and inlineThe tool offers both an embedded and a hosted version, which behave differently. Both paths run and resolve to the same conversion.

How fields are read

Converly does not require you to map the fields in your form (i.e. you don't have to manually tell us which field contains the name or email). Converly works out which field contains the email address, the phone number, the name, etc by scoring each field and taking the strongest available signal.

Signal precedence

SignalStrengthWhat is matched
Input typestrongesttype="email" and type="tel" are treated as definitive.
Autocomplete attributestrongautocomplete="email", tel, given-name, family-name and name are read where present, including the variants some form libraries emit.
Field name or idweakestUsed only when the first two are absent. Names are normalised before matching, so first_name, firstName, First-Name and FirstName all resolve to the same field.

Email address, phone number, first name, last name and full name are identified this way. A field that matches nothing is left alone rather than guessed at.

Sensitive fields are excluded before their value is ever read, by type, by name and by visible label. See What happens to personal data.

Forms that reload the page

Certain form tools submit by reloading the page, which destroys the work the Converly script is in the middle of doing. And leaving the page cannot be treated as success either, because the page reloads whether the submission succeeded or failed.

In this case, Converly takes a snapshot of the lead's name, email, phone, etc when the visitor submits the form, carries it across the page reload, and then reads the outcome from what the tool rendered on the next page.

If a success message is shown on the next page, a conversion is deemed successful and is sent to your chosen ad platforms and analytics tools. If an error message is shown (like if the visitor forgot to complete a required field), then no conversion is sent.

For form tools that use this method, Converly maintains a list of what the success and error messages look like and regularly tests it on live versions of the form to detect changes in the way the form tool shows these messages.

Forms that redirect

Where a form redirects to a thank-you page, work still in flight is carried across the redirect and completed on the next page. Anything already sent from the server is not repeated.

Category 2: Sent by the tool

Tools in Category 2 render the form inside an iFrame on your website, which means the lead's name, email, phone, etc cannot be read from the form directly.

Instead, the success message that the tool sends (usually a postMessage to the browser) provides them, so Converly reads them out of that message.

From a Converly customer perspective, this is nearly identical to Category 1 (reading them from the page). The conversion is still only recorded when the submission is confirmed successful, the same data is captured, and the conversion is sent to your chosen ad platforms and analytics tools in near real-time. The difference is only in where we get the data from.

But because the message arrives from outside your page, it is validated before anything is accepted. Here's how we validate it:

What is checked before a message is trusted

CheckRuleWhat it prevents
Originthe platform's own domain, over HTTPSA script on your page claiming to be the platform, and a message arriving over plain HTTP.
Sourcethe frame Converly is expectingAnother frame on the page forging a success message.
Versiona message shape we recogniseAn unrecognised payload being interpreted as if it were understood.
Site keythis site's own keyA message intended for a different site being accepted as yours.

Category 3: Fetched from the tool

Tools in Category 3 send a success message when a form is successfully submitted (usually a postMessage to the browser), but do not provide the lead's name, email, phone, etc as part of the success message.

In this case, Converly records the conversion in our database as soon as it happens, and it saves the data that is only available in the browser (like the Google Click ID, Facebook Click ID, User Agent, etc).

It then fetches the contact details from the tool separately (usually via an API call or a Webhook) and joins the two together.

The below table shows how the details are retrieved and how they are joined.

MethodWhat it usesHow it works
Platform's own IDthe success messageThe tool's success message carries its own record ID, such as a booking ID or a form submission ID. Converly uses that ID to fetch the submission from the tool's API. Tools that use this method include Calendly, Cal.com and Typeform.
Tracking identifierhidden fieldFor tools whose success message carries no ID. Converly generates an identifier, passes it into the form through a hidden field, then queries the tool's API for the submission carrying it. Needs the hidden field adding to the form once, per form. Tools that use this method include Jotform.
Conversion tracking snippetplatform code fieldWhere the tool offers a field for conversion tracking code, a Converly snippet posts from inside the tool's own frame and the Converly code on the page receives it. Nothing is added to the form itself. Tools like Acuity Scheduling work this way.

Although the conversion is assembled over multiple steps, it actually happens quite quickly:

StepTypical timeWhat happens
Conversion recordedimmediatelyThe conversion is recorded as soon as the platform confirms the submission was successful, before any details are fetched.
Lead details retrieved1 to 5 secondsIt usually takes between 1-5 seconds for the platform's API to return the details of the submission.
Halves joinedunder 100 msWhere both halves arrive together, they are matched and promoted to a conversion.

So for tools in Category 3, a conversion is typically sent to the ad platform or analytics tool anywhere between 1-5 seconds after it happened.

That being said, the timestamp sent with the conversion is the one recorded when the submission initially happened, so an ad platform or analytics tool will record the exact time the conversion happened (even if it was actually sent to them 5 seconds later).

When only one half arrives

In rare cases, one half of a Category 3 conversion never makes it to our servers. What Converly does depends on which half is missing, and the two are treated differently on purpose.

If the tool sends us the lead's name, email, phone, etc., then Converly will still send the conversion with just that information. Ad platforms know who has clicked your ads, so sending the conversion with just this data can still result in a match (assuming the user entered the email address or phone number linked to their account on that ad platform). In this scenario, information that is usually retrieved from the browser (like the click identifiers, user agent, etc) is not sent.

If the browser's half arrives and the tool's half never does, nothing is reported. This is a deliberate measure as typically the browser only knows a form is submitted or a meeting is booked, but it does not know it was confirmed. It may have been rejected due to invalid data, not passing a spam check, etc. So if the tool does not provide the user's details, then we consider that form submission/meeting booking failed and do not send a conversion.

Category 4: Reported server to server

Tools in Category 4 do not allow outside code (like Converly's JavaScript snippet) to be placed on the page where the conversion happens (Stripe's hosted checkout is a common one).

They do, however, allow data about the lead (name, email, phone, etc) to be retrieved via API calls, Webhooks or a dedicated app/integration.

What data is available in this category As you'll see in the table below, the fact the Converly JavaScript code is not installed on the page where the conversion happens changes what data is available to us.

DataAvailabilityWhy
Contact detailsAvailableProvided to us by the tool via API calls, Webhooks or a dedicated Converly app/integration.
Click identifiersNot AvailableBecause Converly's JavaScript code cannot be placed on the page where the conversion happens, we have no access to the ad platforms' click IDs as they are only available in the browser (and our code isn't running in the browser on the conversion page).
IP and user agentNot AvailableBecause Converly's JavaScript code cannot be placed on the page where the conversion happens, we have no access to the IP address and User Agent as they are only available in the browser (and our code isn't running in the browser on the conversion page).

What gets sent to each destination Because certain information is not made available to us with each conversion in Category 4 tools, it changes what data is sent to each destination.

DestinationBehaviourWhy
MetasentReported through the Conversions API on contact details alone, which Meta supports.
Google AdssentRequires the conversion action to be set up for enhanced conversions for leads rather than for web, because there is no browser conversion for it to enhance.
GA4not sentGA4 attributes on session, and there is no session. Sending would create a conversion attributed to nothing, so Converly does not send one.
Other destinationssentReported from the server on contact details, in the same shape as Meta.

Category 5: Custom conversions

Categories 1 to 4 show how we detect conversions in known third-party tools like Calendly, Gravity Forms, Typeform, etc.

However, Converly also provides an API that you can use to send conversions to us from custom-built forms and applications. Common examples include a SaaS app that wants to send a conversion only after the user verifies their email address, or a website with its own custom-built 'Request A Quote' tool that wants to send a conversion once the quote is generated.

Why your code reports it rather than ours

A conversion is only recorded once the thing actually succeeded, and here your backend is the only place that knows.

Converly's JavaScript code can see somebody click the button on your signup page. It cannot see whether the account was created, whether the card was declined, whether the email address was already taken, or whether the trial actually started. Your backend knows all of that, so your backend is what reports it.

The problem this creates

Your backend knows who signed up. It does not know they arrived from a Google ad three days ago, because click IDs only exist in the browser and your server never saw them.

So a Category 5 conversion arrives in two halves, and Converly joins them back together.

The two halves

HalfWhere it comes fromWhat data it provides
BrowserConverly's JavaScript code on your signup pageThe Google Click ID, the Facebook Click ID, the Facebook browser cookies, the page URL and the referrer. Everything that only exists in the browser.
BackendYour own code, calling Converly's APIThe lead's name, email, phone, etc, and the confirmation that the thing actually happened.

The two are tied together by a correlation token. When the visitor reaches your signup page, Converly's JavaScript code generates a random token, sends it to Converly along with everything it captured from the browser, and writes it to a short-lived cookie on your domain.

Your backend reads that token off the incoming request and passes it back when it reports the conversion. Converly matches the two halves on the token and sends one conversion carrying both.

The SDK

You can call the API directly, but Converly publishes a server-side SDK that handles the signing, the retries and the cookie reading for you.

Shell
npm install @converly/sdk-node

It runs on Node 18 or later, has no runtime dependencies, and ships in both module formats. You create a client once using the three values your Converly dashboard gives you, then use two methods.

What the SDK handles

MethodWhat it does
readCorrelation(req)Pulls the correlation token out of the cnv_signup_correlation cookie. It accepts an Express request, a Fetch Request, or a framework's own cookie object, so it works the same way on Next.js, Astro, SvelteKit, Remix and Express.
completeSignup(payload)Signs the request and sends it, then tells you what actually happened rather than just succeeding silently.

Despite the name, completeSignup reports any conversion your backend confirms. It is named that for historical reasons. For a lead form you use it exactly the same way, with a customer_event_id such as lead_4471.

The step that is easiest to miss

The correlation cookie only exists if the page captured conversion intent in the first place, so you have to mark the thing that starts the conversion:

HTML
<form data-converly-signup-intent>...</form>

Or call window.__converly.captureSaasSignupIntent() from your own JavaScript, for a single page app route change or an SSO button that is not a form at all.

Without this there is no token to read, readCorrelation returns nothing for every visitor, and conversions either fire at reduced match quality or not at all. It is the most common reason an otherwise correct integration produces nothing.

If your backend sits on a different domain from your site, the cookie will not be on the request for your server to read. Converly's JavaScript code exposes window.__converly.getCorrelationToken() for that case, so your front end can read the token itself and pass it to your backend along with everything else.

When something goes wrong

A Converly outage never becomes an exception inside your signup handler. Server errors, rate limits and network timeouts are retried three times behind the scenes, waiting one second, then three, then nine. A 4xx response fails immediately instead, because a rejected request is a configuration problem rather than a temporary one. Every retry is signed fresh, so a slow run of retries cannot drift outside the five minute signature window.

What your backend sends

Fields in the call

FieldRequiredWhat it is
correlation_tokenyes, in practiceThe token Converly's JavaScript code put in the cookie. This is what connects the conversion to the ad click.
customer_event_idyesYour own identifier for this conversion, such as account_created_8842. Used to make sure it only ever fires once.
emailyesThe lead's email address.
phonenoThe lead's phone number.
first_name, last_namenoThe lead's name.

Send these raw, exactly as the customer entered them. Do not hash them first. Each ad platform normalises contact details differently, and a value that has already been hashed cannot be re-normalised for a second platform. Converly hashes per destination. See Normalization and hashing.

Making sure it only fires once

The customer_event_id you choose is what stops a conversion being counted twice.

Converly keeps a durable record of which identifiers have already fired, held for 18 months. So if your backend retries after a network timeout, or your code path runs twice, or a returning customer logs in months later and your own guard misses it, the conversion still only reaches your ad platforms once.

This matters more here than anywhere else on this page, because in Categories 1 to 4 the conversion is a one-off event that happened in a browser. In Category 5 it is a call your own code makes, and code gets retried. The SDK itself retries on a server error, so the most likely source of a repeat call is Converly's own client library rather than anything you wrote.

Order and timing

Neither half has to arrive first, and neither has to wait for the other.

What happens in each case

SituationWhat happens
Browser half arrives firstThe usual case. Converly holds it for 24 hours waiting for your backend to confirm.
Backend half arrives firstHeld for about five minutes for the browser half to catch up. If it lands in that window the conversion fires with full attribution, and if it never does the conversion is still sent on contact details alone.
Backend half arrives much laterCommon where an account only becomes real after email verification. Inside 24 hours you get the full conversion with click IDs. Beyond that it is sent on contact details alone.

The practical version is that you call the API whenever the conversion genuinely becomes true, rather than trying to call it at the same moment the visitor is on the page.

When there is no browser half

Sometimes the browser half never arrives. The visitor uses an ad blocker, or sends a Global Privacy Control signal, or signs up directly without ever landing on a page carrying your Converly snippet.

By default, Converly fires nothing in that situation rather than sending a conversion with no attribution attached to it. In the SDK this is the behaviour when no correlation token is found, and the call simply does nothing rather than failing.

If you would rather have the conversion anyway, matched on contact details alone, set allow_uncorrelated to true on the call. That is worth doing deliberately rather than by default, because it also fires for accounts created by an admin, by a backfill script, or by anyone who never visited your marketing site at all.

Authentication

The call from your backend is signed, and the signature covers the method and the path as well as the payload, so a captured request cannot be replayed against a different endpoint. Signatures are only accepted for five minutes, so a captured request cannot be replayed later either.

The SDK does this for you, so signing is only something you need to implement if you are calling the API directly. See Security and access.