Google Ads clicks and GA4 sessions are different measurements. A gap can come from repeat interactions, collection failures, attribution or report scope. Subtracting sessions from clicks does not calculate bot traffic or money wasted.
Find which question is failing: did the landing page load, did analytics collect activity, did the visit receive the expected campaign attribution, and did the business receive an enquiry? Use a controlled journey to locate the break.
1. Name the denominator before calculating a gap
| Measurement | What you are counting | Do not substitute |
|---|---|---|
| Google Ads clicks | The ad report’s click metric for the selected campaigns and dates | Site requests, people or invoices |
| GA4 sessions | Session activity under GA4’s reporting rules | Page views or event rows |
| Engaged sessions | A subset meeting the property’s engagement definition | Every landing visit |
| Server requests | Requests visible at the selected edge or origin layer | Unique ad clicks or GA4 sessions |
| Accepted leads | Enquiries accepted by your application and received by the business | A form-button click or short session |
Two ad clicks can fall inside one session. A later return can contribute another attributed session without another ad click. Either direction of mismatch is possible. A universal “normal discrepancy percentage” would hide differences between sites, campaign formats and measurement setups.
Google’s clicks and sessions troubleshooting explains collection and tagging differences. Checked 3 November 2026. Its cross-platform checklist includes legacy terminology; inspect the actual GA4 property and current report labels.
2. Freeze the report scope
- Record the accounts. Save the Google Ads customer/campaign IDs and the GA4 property and web stream being compared. Verify the actual account link and its effective dates.
- Record both timezones and date ranges. Compare completed periods after normal processing. A calendar day in one timezone is not the same interval in another.
- Record dimensions and filters. Keep campaign, network, device and landing-page selections explicit. Do not compare all Ads traffic with one filtered GA4 channel.
- Record the GA4 metric. Sessions, engaged sessions, users, page views and key events answer different questions. Keep first-user acquisition separate from session acquisition.
- Record the reporting surface. A standard report, Exploration, raw export and third-party dashboard can have different definitions or processing. Note active filters, consent/modeling settings and any displayed data-quality message.
Keep one before/after comparison sheet with these fields. Do not change attribution settings simply to make two totals look equal. For conversion rather than session discrepancies, use the collection, delivery and attribution diagnostic.
3. Trace one landing journey before changing the campaign
- Open the landing URL directly on the intended device. Use a synthetic test, not repeated paid-ad clicks. Include harmless test parameters and mark any submitted lead as a test.
- Observe the whole redirect chain. Check protocol/domain changes, language routing, link wrappers and the final page. A rewrite or redirect can remove parameters or route to an untagged page.
- Check page and tag behavior. Use browser developer tools and Google Tag Assistant to inspect load failures, the intended measurement destination and collection behavior for that page.
- Test consent choices independently. Observe the implementation’s expected behavior under each choice. Respect the visitor’s choice; do not bypass consent to inflate measurement.
- Trace a valid enquiry separately. A tag request is not proof of server acceptance or CRM receipt. Check the business record and retry path.
The following synthetic redirect retains UTMs but loses its GCLID. The free checker compares the two strings locally:
Before:
https://example.com/quote?gclid=adp_test_only&utm_source=google&utm_medium=cpc&utm_campaign=tracking_test
After:
https://example.com/quote/?utm_source=google&utm_medium=cpc&utm_campaign=tracking_testPasted URLs stay in this browser. No URL visits, uploads or saved inputs.
Paste complete http:// or https:// URLs, including their query strings. For a comparison, use URLs from the same test journey. Remove personal information first.
Checks the URLs you paste. Use Google Ads’ URL test and browser diagnostics to verify live redirects, tag loading and consent.
Parameter preservation is one checkpoint. This checker does not fetch a URL, execute a tag, validate consent or establish an ad charge. Use the actual browser journey to check those behaviors.
For log evidence, select one observation layer and the landing-document path. A cached response can be visible at the edge without reaching the origin. The repeat-request exercise separates requests and observed click identifiers; the IP review tool helps format selected addresses after investigation.
Need to locate a lost click ID? Use the GCLID redirect trace and runnable local HTTP example to separate query loss from tag and storage failures.
4. Is activity missing, or only its campaign label?
Compare total relevant landing activity with its paid-campaign subset. If the page activity exists but the expected source/campaign is missing, inspect attribution and linking before calling it an unrecorded visit.
- Verify auto-tagging and the actual linked property. Check historical link dates instead of assuming the current link populated earlier reports.
- Inspect the final URL after redirects. Review missing or conflicting manual campaign tags; test a consistent UTM fallback alongside the intended auto-tagging setup.
- Review
(not set)and processing indicators. An unavailable dimension is not itself proof of invalid traffic. - Keep Session source / medium distinct from first-user dimensions. A person’s first acquisition source does not describe every later visit.
For Demand Gen, also check the native interaction and attribution types. Its channel and view-through setup needs the Demand Gen diagnostic; a PMax placement report is not a substitute.
5. Run a ten-row session-counting exercise
In raw data, a session-number value alone is not a visitor/session key. Two browsers can start in the same second. In this synthetic example, Browser A and Browser B share the value 1710000000, while Browser A also has a later session.
Download the SQLite session-counting demo, save it in your current folder and run this command with Python 3.9 or later. It creates a fresh in-memory database, writes no database file and contacts no service:
python3 - <<'PY'
import sqlite3
from pathlib import Path
connection = sqlite3.connect(':memory:')
sql = Path('ga4-session-counting-demo.sql').read_text()
setup, report = sql.split('-- BEGIN REPORT\n', 1)
connection.executescript(setup)
for metric, value in connection.execute(report):
print(f'{metric}: {value}')
PYInspect the complete SQL and synthetic records
-- Synthetic SQLite exercise, not a GA4 export query or advertiser results.
-- Run in a fresh in-memory database using the Python command in the guide.
-- Scope: one selected web stream/property and comparison window.
-- row_id is a local fixture key; GA4 does not supply this synthetic column.
-- No clicks, charges, fraud rate or physical people can be inferred from keys.
CREATE TEMP TABLE adp_demo_ads (reported_clicks INTEGER NOT NULL);
INSERT INTO adp_demo_ads VALUES (12);
CREATE TEMP TABLE adp_demo_events (
row_id INTEGER PRIMARY KEY,
user_pseudo_id TEXT,
ga_session_id INTEGER,
event_name TEXT NOT NULL
);
INSERT INTO adp_demo_events VALUES
(1, 'browser-a', 1710000000, 'session_start'),
(2, 'browser-a', 1710000000, 'page_view'),
(3, 'browser-a', 1710000000, 'scroll'),
(4, 'browser-b', 1710000000, 'session_start'),
(5, 'browser-b', 1710000000, 'page_view'),
(6, 'browser-a', 1710003600, 'session_start'),
(7, 'browser-a', 1710003600, 'page_view'),
(8, 'browser-c', 1710007200, 'page_view'),
(9, NULL, 1710010800, 'page_view'),
(10, 'browser-d', NULL, 'page_view');
-- BEGIN REPORT
WITH keyable AS (
SELECT * FROM adp_demo_events
WHERE user_pseudo_id IS NOT NULL AND user_pseudo_id <> ''
AND ga_session_id IS NOT NULL
), session_groups AS (
SELECT user_pseudo_id, ga_session_id,
MAX(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS has_start
FROM keyable
GROUP BY user_pseudo_id, ga_session_id
), metrics AS (
SELECT 1 AS ordinal, 'ads_report_clicks' AS metric,
(SELECT SUM(reported_clicks) FROM adp_demo_ads) AS value
UNION ALL SELECT 2, 'collected_event_rows', COUNT(*) FROM adp_demo_events
UNION ALL SELECT 3, 'session_start_events', COUNT(*) FROM adp_demo_events
WHERE event_name = 'session_start'
UNION ALL SELECT 4, 'distinct_session_ids_among_keyable_rows',
COUNT(DISTINCT ga_session_id) FROM keyable
UNION ALL SELECT 5, 'distinct_visitor_session_pairs', COUNT(*) FROM session_groups
UNION ALL SELECT 6, 'events_missing_either_key', COUNT(*) FROM adp_demo_events
WHERE user_pseudo_id IS NULL OR user_pseudo_id = '' OR ga_session_id IS NULL
UNION ALL SELECT 7, 'keyed_groups_without_session_start', COUNT(*) FROM session_groups
WHERE has_start = 0
)
SELECT metric, value FROM metrics ORDER BY ordinal;
-- END REPORT| Output metric | Value | Interpretation |
|---|---|---|
ads_report_clicks | 12 | The separate synthetic Ads report; not a session or charge count. |
collected_event_rows | 10 | Several events can belong to one session. |
session_start_events | 3 | Observed start events, not every keyed group. |
distinct_session_ids_among_keyable_rows | 3 | Browser A and B share one session-number value. |
distinct_visitor_session_pairs | 4 | Group both fields inside this one-stream fixture. |
events_missing_either_key | 2 | Unkeyable records remain visible. |
keyed_groups_without_session_start | 1 | Browser C has a page view but no start in the selected data. |
Grouping the two non-empty key fields finds four observed pairs, while counting the session value alone finds three. Two events lack a complete key and stay in a separate count. A keyed group can lack a session_start inside the selected data; that observation does not establish why.
Google’s session documentation describes combining visitor and session identifiers and explains estimated UI counts. Raw-export counts need not equal every GA4 reporting surface.
This is a counting exercise, not an attribution reconstruction
The twelve-click report is deliberately separate. The SQL does not link those clicks to sessions, calculate a bot percentage, recover missing keys or reproduce GA4 modeling. The ten records are not customer data. A missing key does not identify a consent choice, a bot or a physical person.
For a real export, extract ga_session_id from event_params and select a consistent visitor field such as user_pseudo_id. Scope the property, stream and dates first; keep missing-key rows visible. Do not create artificial keys to force a match.
The GA4 BigQuery export schema defines the repeated parameter and privacy fields. Its daily tables can receive late updates for up to three days. The download is SQLite with flat synthetic columns, not a query for your live BigQuery dataset.
6. Choose the fix that matches the evidence
| Finding | Next test | Success evidence |
|---|---|---|
| Landing page or tag fails in a controlled journey | Repair that page/redirect/tag path and repeat the same test | Page loads and expected collection occurs under the chosen consent state |
| Activity exists under an unexpected source | Review preserved parameters, linking and report scope | The expected source is recorded for a controlled eligible test |
| Raw event rows were treated as sessions | Separate events, complete-key groups and missing keys | Repeatable counts with explicit scope and unresolved records |
| Sessions exist but the business receives no leads | Test form acceptance, delivery and offer fit | Received and qualified enquiries, not just a larger analytics total |
| Suspicious visits remain after measurement checks | Review request behavior and reliable identity/network evidence | A documented protection decision and its actual enforcement status |
Inspect Google Ads’ invalid-traffic reporting and billing adjustments separately. A site classification or missing GA4 session is not an invoice line. The native-protection comparison explains filtering, exclusions and website protection.
Where AdProtektor fits
AdProtektor adds evidence about visits its integration observes. ConversionOS records supported events and delivery status; configured fraud checks can withhold events associated with blocked visitors or crawlers. It cannot reconstruct an analytics event that was never collected or force platforms to agree on attribution.
Start a free AdProtektor trial to review observed visits and configured protection alongside native reports and received business outcomes. Use the conversion-action audit if the problem is what the campaign counts as a conversion.
If Ads reports clicks but you cannot find a landing request, use the missing-arrival diagnostic to check click types, destinations, CDN/cache behavior and log coverage before interpreting the gap.