Every content team eventually gets asked the same question: which articles actually bring us customers? And most teams answer it with a screenshot of organic sessions, because that is the last number they can see without help. The trouble is that sessions are a vanity metric for content. A post with 40,000 visits and zero signups is a cost, not an asset.
The fix is a funnel that starts at the search impression and ends at the signup row in your database. It is less work than it sounds. You need three sources, one join key, and a couple of honest assumptions.
Why the funnel usually breaks
Each tool sees one slice and uses its own identifiers. Search Console reports by query and page URL, aggregated daily. GA4 reports sessions by landing page with its own client id. Your product database has a users table with a signup_source column that someone populated three different ways over the years.
- URLs differ in trailing slashes, query strings and casing, so naive joins silently drop 10–30% of rows.
- GA4 sampling and consent mode mean session counts are estimates, not ledgers.
- Signup attribution is usually first-touch at best, and only for users who did not clear cookies.
None of these are dealbreakers. You just need to normalise the join key and be explicit about which numbers are exact and which are modelled.
Step 1 — Connect the three sources
In Kimo, connect Search Console and GA4 with OAuth, then add your product database with a read-only user. Search Console backfills 16 months on first sync; set the cadence to daily. For the database, only grant access to users and subscriptions — you do not need anything else for this funnel.
Step 2 — Model the join on a clean page key
The whole funnel hangs on one normalised key: the page path, lowercased, without query string or trailing slash. Create it once in a data model and every downstream chart inherits it.
with search as (
select
lower(regexp_replace(page, '(\?.*)|(/$)', '')) as page_key,
sum(impressions) as impressions,
sum(clicks) as clicks
from gsc.search_analytics
where date >= current_date - interval '90 days'
group by 1
),
sessions as (
select
lower(regexp_replace(landing_page, '(\?.*)|(/$)', '')) as page_key,
count(*) as sessions
from ga4.sessions
where session_start >= current_date - interval '90 days'
and channel_group = 'Organic Search'
group by 1
),
signups as (
select
lower(regexp_replace(landing_path, '(\?.*)|(/$)', '')) as page_key,
count(*) as signups,
count(*) filter (where plan <> 'free') as paid
from app.users
where created_at >= current_date - interval '90 days'
group by 1
)
select
s.page_key,
s.impressions,
s.clicks,
coalesce(se.sessions, 0) as sessions,
coalesce(su.signups, 0) as signups,
coalesce(su.paid, 0) as paid
from search s
left join sessions se using (page_key)
left join signups su using (page_key)
order by signups desc;Then group pages into clusters — a dimension that maps paths to topics like /blog/attribution-* or /compare/*. Clusters are what you will actually make decisions on; individual URLs are too noisy below a few hundred sessions.
Step 3 — Read the funnel by cluster
Now the interesting part. When we ran this model on a simulated B2B SaaS workspace, the ranking by sessions and the ranking by signups were almost inverted. Glossary pages drove traffic. Comparison pages drove customers.
- Search CTR
- Signup rate
This is the chart that changes an editorial calendar. Instead of “write more glossary posts because they rank”, the team can argue for comparison pages with a number attached — and later check whether the bet paid off.
Step 4 — Put alerts on the leaks
A funnel you look at once a quarter is a report. A funnel that tells you when it breaks is a system. Add three alerts in Kimo and you will catch most problems before they show up in pipeline:
- Impressions drop > 25% week over week on any cluster — usually an indexing or canonical issue.
- CTR drops > 20% while position is stable — a competitor rewrote their snippet, or yours got truncated.
- Signup rate drops > 30% on a page with steady sessions — a broken form, a slow page or a changed CTA.
The afternoon checklist
| Step | Time | Output |
|---|---|---|
| Connect GSC, GA4, product DB | 20 min | Three healthy sources |
Build content_funnel model | 45 min | One row per page key |
| Add cluster dimension | 30 min | 8–12 topic clusters |
| Funnel dashboard | 30 min | Shareable link for the team |
| Three alerts | 15 min | Slack pings on leaks |
That is it. One afternoon, and the next time someone asks which content brings customers, you send a link instead of a screenshot. If you want to see the finished version, the SEO dashboard in the live demo is built exactly this way on simulated data.
- #SEO
- #Attribution
- #GA4
- #Search Console
Writes about GEO, SEO, AI search, Attribution.
People, companies and figures in this article are illustrative; charts use simulated data.



