Q. Why is the click count reported by an ad platform different from the session count in GA4?
GA4 starts counting at the moment someone arrives on your site. Anyone who clicked the link but disappeared before arriving never shows up in any report. To measure that stretch, the link has to stop somewhere you control instead of going straight to the destination, and that is exactly what tracking links and MMPs do.
Three things to do today
- Put ad platform clicks and GA4 sessions side by side for the same period and measure the gap first
- If app installs are not in your funnel, one redirector is enough, you do not need an MMP
- For owned media you cannot instrument, run the in and out links through the same tracking domain to stitch them
GA4 starts counting at arrival
You check the click count in the ad manager. You check sessions in GA4 for the same period. The numbers do not match. This happens in almost every account, and the first reaction is usually to assume one of the two is broken.
Both are fine. They were never measuring the same thing.
GA4 only produces data once the tag you installed actually runs. It exists from the moment a person arrives, the page opens, and the script executes. Whatever happened before that is outside its range of observation, so it does not even show up as a blank.
And a lot happens in that gap.
- They clicked, the landing page was slow, they went back
- The in-app browser in a messenger or Instagram failed to open the link properly
- One more redirect was involved and tags like
utm_sourcefell off along the way - The ad platform's definition of a click and GA4's definition of a session were never the same
All four need different fixes. The first is a page speed problem, the second is a link format problem, the third is a tagging problem, and the fourth is simply normal. Look only at GA4 and you cannot tell them apart. They all collapse into one lump called sessions are lower than expected.
Measurement has six layers and we switch on at the third
It helps to split measurement into layers. Between an ad being shown and revenue being recorded, a person passes through six doors.
Most of what GA4 and GTM do sits between L2 and L4. L0 is reported by ad platforms and Search Console, L5 runs separately in the CRM. The layer nobody counts is L1.
If the layer model itself is new to you, start with the five-layer measurement audit. This piece adds one more layer outside the site on top of it.
Why is L1 the blank one? Because nothing is responsible for recording the fact that a link was clicked. Ad platforms only know the clicks on ads they served, and a link placed in a chat notice, a video description, or a partner blog is nobody's observation target.
So those links drain into the dark funnel. They are not invisible because they produce nothing, they are invisible because nothing was installed to look.
What happens when a link stops at a server first
There is really only one way to measure L1. Do not send the link straight to its destination, make it stop at a server you control.
This is also how a URL shortener counts clicks and where an MMP begins attribution.
The moment it stops there, the server receives one request carrying the time, the IP, the browser, and where the click came from (the referrer). The server records that and answers with a 302 telling the browser the real destination. To the user it just looks like the link opened.
The decisive part is that the record lands on a server rather than in the browser. Someone who blocks cookies, someone running an ad blocker, someone who hits back before arriving, all of them already counted as a click. That is the stretch GA4 structurally cannot see.
Watch out. Adding one redirect hop can knock UTM tags off. You have to confirm the server forwards parameters that were already on the destination URL. The failure mode and the defense are written up in the piece on UTM persistence.
What an MMP adds on top
Counting clicks with a redirect is something URL shorteners already do. An MMP (Mobile Measurement Partner) adds the app on top of it. Airbridge, AppsFlyer, Adjust, and Branch are products in that same market.
Faster to look at the structure than to describe it. An Airbridge tracking link looks like this.
The user arrives with the server already knowing which campaign on which channel the click came from.
If you have an app, this is where the real leverage appears. Click to store, store to install, install to first open, that stretch is one browser cookies can never bridge. A browser and an app are separate boxes. Connecting those two boxes is an MMP's core job.
On the web side, deeplink_url sends people who already have the app into an app screen, and the fallback_ family routes everyone else to the web or the store. If you only care about web, you install the Web SDK, which works as an npm module or through GTM. It automatically stores the UTM values carried by the tracking link so campaign source is captured.
To be straight about it. I have never operated Airbridge. Everything in this section about link structure, parameters, and pricing comes from the official documentation, and nothing here reflects a feel for how the numbers actually read in the dashboard. If you are evaluating it, take the structure from here and get the operational feel from someone who has run it.
The fork is whether you have an app
Whether to buy an MMP is not settled by a feature comparison. It is settled by one question.
Is there a stretch in my funnel that a browser cannot bridge on its own?
If app installs are in the funnel, that stretch exists. There is no other way to connect the browser that clicked the ad with the person who first opened the app, and since that is an MMP's reason for existing, there is no real substitute.
If you collect leads or consultation requests on the web, that stretch does not exist. Click to landing to form submission all happens inside the browser, usually inside one domain. That is territory UTM and GA4 already cover.
Image: The two roads differ in scale, not in quality. Pick the big one without an app and you pay for capability you never touch.
Pricing points the same way. Airbridge charges on monthly active users who have opened the app at least once, and organic traffic and web traffic are not billed. A web-only service adopting an MMP to measure only web ends up using a tool whose center of gravity sits somewhere other than its problem.
| Your situation | How to measure L1 | Why |
|---|---|---|
| App installs are in the funnel | MMP | Nothing else bridges browser to app |
| Web only, many channels, several people making links | MMP or your own redirector | The real driver is link governance, not measurement |
| Web only, few channels, you make every link | Your own redirector | All you need is a click log and a join key |
| You just want click counts | URL shortener | No reason to buy more if you never join to conversions |
Crossing someone else's platform: what breaks and what doesn't
This is where the subject gets misread most often, and there are two cases to separate. The dividing line, though, is not the one people usually reach for.
Image: You cannot see the middle. Put identical gates at both ends, though, and you can tell that the light going in and the light coming out are the same light.
The test is where you can place links, not who owns the site
What splits the two cases is not "do I own that site" but can I place the links going in and out of it.
If it is someone else's post, a third-party page you cannot touch, it genuinely breaks. You know they went in, but you cannot create the link that brings them out, so there is no hook to attach the rest of the trip to.
In practice the far more common case is the other one. The blog we run on a platform, our own video channel, our own messenger channel. The platform belongs to someone else but the content and the links inside it are ours. This is owned media, and here you cannot see inside, but the entrance and the exit are both in your hands.
The test is not whether the platform is yours, it is whether you can create the link that leads out of it.
You do not need to know what happened inside the blog. You only need to connect going in with coming out.
Why the cookie comes back
A fair objection at this point is that third-party cookies are supposed to be blocked, so how does this work. It works because this is not a third-party cookie.
What decides blocking is not the kind of cookie but who owns the address bar at that moment.
The moment someone clicks the first link and lands on go.example.com/a, the server issues a visitor number, stores it as a cookie on that domain, and logs the click. The user moves on to the blog.
When that person clicks the second link inside the blog and arrives at go.example.com/b, the browser sends the cookie it stored earlier, because it is the same domain. From the server's side the same visitor number appears twice, so the two clicks belong to one person.
What matters is that both hops are navigations where the address bar itself changed to my domain. The third-party cookies browsers block are cookies read while embedded inside someone else's page as an iframe or a pixel. Arriving directly at your own domain is a different situation. That is why you do not need any tracking code inside the blog. Only the links in and out have to pass through your domain.
Carrying it through to conversion
To reach conversions on your site, you have two options. Either the second link passes the visitor number along in the URL, or, if the tracking domain and the site sit under the same parent domain, you share the cookie.
Set-Cookie: visitor_id=abc123;
Domain=.example.com; // go.example.com and www.example.com both read it
Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=15552000
Records then accumulate in two places, with the same visitor number running through both.
The three off-site events belong to the tracking server, the two after arrival belong to GA4. One visitor number is all that binds them.
visitor abc123
external_link_click source=chat_notice
owned_media_enter media=blog content_id=post_001
owned_media_exit media=blog link_id=main_cta
landing_view page=/pricing
submit_lead value=...
The gaps between steps are recorded too, so you can see whether someone who saw the notice comes back days later by way of the blog. These two events (owned_media_enter and owned_media_exit) should not just be bolted on, they need a proper place in the event scheme, and that design is covered separately in the event taxonomy piece.
Three places this quietly falls apart
It is not 100 percent, and the failure mode is nasty. It does not stop with an error, it just records a different person.
None of the three throws an error. They quietly count as a different person, which is hard to notice from the report alone.
First, the browser changes. This is the big one in practice. Someone clicks the first link inside a messenger's in-app browser, the blog opens and hands off to that platform's own app, and the second link is clicked in a different browser entirely. Same human, two visitor numbers.
Second, browsers actively target this pattern. The structure relies on a domain that only redirects and moves on, which is exactly what bounce tracking mitigations look for. Safari's ITP counts the top-frame redirects a domain performs and classifies it on that basis, and a domain classified as a bounce tracker may have its cookies rewritten to SameSite=Strict. Strict means the cookie is not sent on a navigation arriving from another site, which is precisely the hop from the blog into your second link. Classified domains that receive no first-party interaction for 30 days also have their stored data deleted.
Third, the ordinary things. Private browsing, cleared cookies, reading on a phone and returning on a desktop. Nothing prevents those.
So read the number as a floor. If 60 percent connected, the correct reading is not "40 percent dropped off" but "at least 60 percent connected, and the rest is unknown". Leave that out of the report and the next person reads 40 percent as a drop-off rate and goes off to rewrite the content. And because classification-based blocking arrives suddenly, track the connection rate itself over time. A sharp fall may be a signal about your domain rather than your content.
What you are left holding
Here is what remains and what does not.
| You can know | You cannot know |
|---|---|
| How many times the notice link was clicked | How long they read the blog and how far they scrolled |
| Which post you sent them to | Which passage inside the blog worked |
| Which CTA inside it was clicked | Whether a person whose browser switched is the same person |
| Whether the same visitor reached your site | The earlier trip of anyone who cleared cookies |
| Whether they converted, and how many days each step took | The path of anyone who arrived by search instead of a link |
Most of the right-hand column was never needed. The goal was not "did they read the blog well" but connecting the fact that they passed through an unmeasurable stretch with what happened on either side of it.
So this system is not a click counter. It is a way of driving checkpoints into the entrance and exit of owned media you cannot instrument, and the same pattern extends to a video description, a messenger channel, or a partner landing page. You accept that the inside is invisible and capture the passage instead.
Just be careful about calling the stitched path an individual journey without the caveats above. Write only "customer journey" in the report and the team believes in precision that is not there, then moves budget on the strength of it. Same trap as reading attribution models.
Measuring off-page without an MMP
If there is no app and an MMP is overkill, you can build L1 yourself. You need one subdomain and one redirect.
What joins a click log to a conversion log is not a tool, it is a join key.
It looks like code but there is nothing to be afraid of. The whole job is five lines. Take the address, look up the real destination, stamp each click with a unique number, write the record, send them on.
// Cloudflare Workers, go.example.com/:key
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (!key) return new Response('Not found', { status: 404 });
const target = await env.LINKS.get(key); // look up the real destination
if (!target) return new Response('Not found', { status: 404 });
const clickId = crypto.randomUUID(); // unique number per click
const dest = new URL(target);
dest.searchParams.set('click_id', clickId); // carry it to the destination
env.CLICKS.writeDataPoint({ // logging must not block the response
blobs: [key,
request.headers.get('referer') ?? '',
request.headers.get('user-agent') ?? '',
request.cf?.country ?? '',
clickId],
doubles: [1],
indexes: [key],
});
return Response.redirect(dest.toString(), 302);
},
};
The line that matters is the one attaching click_id to the destination URL. Arrive with that value attached and the landing page can pick it up into a GA4 event parameter or a hidden form field. The moment click records and conversion records share a key, the two tables join.
The idea is not new. Google's gclid and Meta's fbclid do exactly this, and it is why you pass those values along when sending server-side conversions. The mechanics are covered in the piece on click IDs and server-side conversions. What you just built is the same structure, aimed at your own channels.
Load it and the opening question gets a numeric answer.
-- Illustrative query. This is the shape to write, not output from a run.
SELECT
c.link_key,
COUNT(DISTINCT c.click_id) AS clicks,
COUNT(DISTINCT s.click_id) AS arrived,
SAFE_DIVIDE(COUNT(DISTINCT s.click_id),
COUNT(DISTINCT c.click_id)) AS arrival_rate
FROM `analytics.link_clicks` AS c
LEFT JOIN `analytics.sessions` AS s USING (click_id)
GROUP BY link_key
ORDER BY clicks DESC
arrival_rate is the answer to this article's first paragraph. The fact that clicks were counted and arrivals were not becomes data in itself, and because it breaks out per link you can see which link on which channel is leaking. Links opened in in-app browsers showing up conspicuously low is how the cause surfaces.
The risk of renting your link infrastructure. Firebase Dynamic Links shut down on 25 August 2025, and every link issued until then, custom domain or page.link alike, now returns a 404. Analytics data not exported beforehand is unrecoverable. Given that links stay parked in video descriptions and old posts for years after you publish them, who controls that domain is an asset question, not a measurement question.
Build or buy comes down to scale. Once there are dozens of link types and several people issuing them, you need an admin screen and permissions, and that is where SaaS starts earning its price. With a dozen links that you create yourself, the code above plus one key-value store stands up in half a day.
The real cost is not on the build side. The cost is keeping the link naming convention from collapsing six months later. That is a convention problem rather than a tooling problem, and the next piece takes it up separately as what happens six months after you start tagging UTMs by hand.
Thirty minutes in your own account today
There is something to do before buying anything, which is measuring your own gap. At 3 percent you can leave it alone. At 30 percent the landing page needs fixing before any tool does.
- Measure the gap: put ad platform clicks and GA4 sessions side by side for the same period and the same campaign. What is the percentage difference?
- Separate the causes: if the gap is wide, start with in-app browser share and landing page load speed. Those two usually explain most of it.
- List the off-page links: write down every link you placed somewhere that is not an ad. Chat notices, video descriptions, email, partner posts. How many of them leave no record at all?
- Settle the fork: are app installs in the funnel? If yes, evaluate an MMP. If no, one redirector is enough.
- Secure a join key: whichever route you take, issue a unique number at click time and carry it to the destination. Without it, no tool you buy will join the two tables.
If you take only one thing from this, take this.
A stretch you never measured is not a stretch with no performance, it is a stretch whose performance you do not know.
This is an advanced entry in the Digital Marketing Analytics series. The full map is in the five-layer measurement audit, and the story after arrival continues in UTM parameter basics and cookies, sessions, and events. Next up is measuring off-site exposure channel by channel, and the record of actually measuring the organic part is in the SERP slot measurement piece.
Sources
- Tracking link structure, parameters, ad_type: Airbridge Help Center, Tracking Link Structure and Parameters
- The abr.ge default domain and custom domains: Airbridge Help Center, Custom Domain
- Pricing basis (app monthly active users, web traffic not billed): Airbridge pricing
- Web SDK installation (npm, GTM) and automatic UTM storage: Airbridge Help Center, Web SDK
- Third-party cookie blocking, ITP, ETP: Airbridge, Web Attribution
- Bounce tracking classification, SameSite=Strict rewrite, 30-day data deletion: WebKit, Tracking Prevention in WebKit
- Firebase Dynamic Links shutdown (25 August 2025, 404 thereafter): Firebase, Dynamic Links Deprecation FAQ
I have never operated Airbridge directly. The tool descriptions here were written from official documentation, and none of it carries a practitioner's feel for the dashboard. The redirector code is a minimum example meant to show the mechanism, and a production version also needs bot filtering, retry handling, and a personal data retention policy. The BigQuery query is illustrative and not output from a run. Connection rates for owned-media checkpoints depend on browser policy, so the only way to know what share actually stitches on your own channels is to measure it.
Found this useful? Share it
Latest posts
Kakao Share Thumbnail Not Showing? Here's the Fix (JavaScript SDK 2025)
Learn why Kakao Share thumbnails don't appear even with a valid imageUrl, and fix it using scrapImage to convert your URL into a KAGE CDN URL. Step-by-step guide.
How to Generate CRM User Manuals in 20 Minutes with Claude Code Sub-Agents
Learn how Claude Code sub-agents can analyze your CRM codebase and auto-generate user manuals and visual guidebooks in just 20 minutes. See the real-world results.
GEO Generative Engine Optimization: The Complete 2025 AI Marketing Strategy Guide
Learn what GEO (Generative Engine Optimization) is, how it differs from traditional SEO, and discover 5 proven AI marketing strategies to get cited by AI search engines in 2025.
Related projects
Get new posts by email
Insights on marketing, analytics, and dev, delivered to your inbox.

