example.comPages that earn sign-ups: Search Console, GA4 and your sign-ups · Claude Code
3 pages with 40 clicks brought 9 sign-ups; the top 12 brought 2
12 pages hold 60% of organic clicks (5,640 of 9,400) and produced 2 sign-ups in 28 days. 3 pages with 40 clicks between them produced 9.
31 organic sign-ups in total; 6 had no stored landing page.
Landing page
Clicks
Sessions
Sign-ups
Per 100 clicks
/compare/acme-vs-ledgerly
18
21
4
22.2
/integrations/quickbooks
14
16
3
21.4
/templates/net-30-invoice
8
11
2
25
/blog/invoice-template
1,920
2,110
1
0.05
/blog/what-is-net-30
1,180
1,240
1
0.08
/glossary/proforma-invoice
640
702
0
0
9 more top-12 pages
1,900
2,050
0
0
First: link /blog/invoice-template (1,920 clicks, 1 sign-up) to /compare/acme-vs-ledgerly, which converts 22 of every 100 clicks.
An example with made-up numbers. Your agent posts your real ones.
What it does
Search Console ranks pages by clicks, GA4 by sessions, and your database knows who actually signed up. None of them tells you that the glossary page with 1,900 clicks a month has produced one account, while a comparison page with 18 clicks produced four. That only shows when the three are joined on the landing page URL.
The agent pulls 28 days from each source, normalizes the URLs so they match, and counts sign-ups per 100 organic clicks for every page. You get two short lists: pages that convert well and deserve more traffic, and high-traffic pages that need a better path to sign-up.
How it works
1Pull organic clicks by page
Call searchanalytics.query with dimensions: ["page"], type: "web", rowLimit: 25000 and the 28 full days ending 2 days ago. Keep clicks, impressions and position for every page.
2Pull organic sessions by landing page
Call runReport for the same dates with dimensions: [{ name: "landingPagePlusQueryString" }], the metrics sessions and keyEvents (or keyEvents:sign_up for the sign-up event alone), and a dimensionFilter on sessionDefaultChannelGroup with an EXACT match on Organic Search.
3Count sign-ups by first landing page
From whichever store you use, read the table and column (or Stripe metadata key) named in settings. In Supabase or Postgres: SELECT first_landing_page, count(*) FROM public.profiles WHERE created_at >= '<start>' AND created_at < '<end>' GROUP BY 1 over the same 28 days, limited, when a referrer or utm_medium column exists, to rows that came from a search engine. In Stripe: list GET /v1/customers?created[gte]=<unix start> and read metadata[<key>]. A stored from slug or utm_content value is mapped to its page with the table in settings.
4
4Join the three on the URL
Reduce every URL to a path: drop scheme and host, the query string and fragment, a trailing slash (keep / for the home page), and lowercase it. Full-join Search Console pages, GA4 landing pages and sign-up pages on that path. Sign-ups with an empty landing page count in the total as unattributed; sign-ups whose page has no organic clicks are listed apart, because they most likely arrived by another channel.
5
5Rank and reconcile
Work out sign-ups per 100 clicks for every page and for the site. Double down on pages with 3 or more sign-ups at three times the site rate or better; fix pages with 300 or more clicks at under a third of it. Where GA4 key events and database sign-ups for one page differ by more than 30%, say so: consent banners and events firing on the wrong page both show up here.
6
6Post the table
Post an update card: the summary names how much of the click volume the top pages hold against their sign-ups, then a table of the best converters and the biggest underperformers, and one callout with the first link or page change to make.
Post it to the board
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
What you get
Click share against sign-ups: how much organic traffic the top pages hold and how many sign-ups they produced.
Pages to double down on: clicks, sessions, sign-ups and sign-ups per 100 clicks.
Pages to fix: high-traffic pages that rarely lead to a sign-up.
Unattributed sign-ups and any page where GA4 and the database disagree.
The first move, usually a link from a busy page to a page that converts.
Install and run it
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
Agent
Data
Schedule
Install the skill
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/google-search-console-pages-that-earn-signups/install | sh
Run it
Ask Claude Code:
promptRun the google-search-console-pages-that-earn-signups skill
Or paste the prompt instead of installing:
promptJoin three sources for the last 28 days of final data on my site. 1) Search Console API: clicks by page. 2) GA4 Data API: sessions and key events by landingPagePlusQueryString, filtered to sessionDefaultChannelGroup = Organic Search. 3) My sign-ups: <table.column or Stripe metadata key> holds the first landing page; count sign-ups per page for the same dates. Reduce every URL to a lowercase path without query string, fragment or trailing slash, and join on it. Count sign-ups with no landing page as unattributed. Give me sign-ups per 100 clicks for each page and the site, pages with 3+ sign-ups at 3x the site rate, and pages with 300+ clicks below a third of it. Note pages where GA4 key events and my sign-up count differ by 30%+. End with the first change to make.
Put it on a schedule
Mondays at 9:00, from cron on any machine where Claude Code is signed in:
crontab0 9 * * 1 claude -p "Run the google-search-console-pages-that-earn-signups skill"
Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Send it to your board
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
name: google-search-console-pages-that-earn-signups
description: "Every Monday: organic clicks, GA4 sessions and real sign-ups for each landing page, joined on the URL, with the pages to push and the pages to fix. Use when the user asks for this, or when it runs on a schedule."
---
# Pages that earn sign-ups: Search Console, GA4 and your sign-ups
Every Monday: organic clicks, GA4 sessions and real sign-ups for each landing page, joined on the URL, with the pages to push and the pages to fix.
Search Console ranks pages by clicks, GA4 by sessions, and your database knows who actually signed up. None of them tells you that the glossary page with 1,900 clicks a month has produced one account, while a comparison page with 18 clicks produced four. That only shows when the three are joined on the landing page URL.
The agent pulls 28 days from each source, normalizes the URLs so they match, and counts sign-ups per 100 organic clicks for every page. You get two short lists: pages that convert well and deserve more traffic, and high-traffic pages that need a better path to sign-up.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- Sign-ups must record where the person first landed: a column such as `first_landing_page` in your database, or a Stripe customer metadata key, filled from the landing URL or a `from` or `utm_content` value at sign-up.
- Read access to the Search Console property and Viewer access to the GA4 property, through MCP servers or a service account with `GOOGLE_APPLICATION_CREDENTIALS`.
- Read-only access to the sign-up store: the Supabase MCP server with `read_only=true`, a read-only Postgres role in `DATABASE_URL`, or a restricted Stripe key with Read on Customers.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Pull organic clicks by page** (Google Search Console): Call `searchanalytics.query` with `dimensions: ["page"]`, `type: "web"`, `rowLimit: 25000` and the 28 full days ending 2 days ago. Keep clicks, impressions and position for every page.
2. **Pull organic sessions by landing page** (Google Analytics): Call `runReport` for the same dates with `dimensions: [{ name: "landingPagePlusQueryString" }]`, the metrics `sessions` and `keyEvents` (or `keyEvents:sign_up` for the sign-up event alone), and a `dimensionFilter` on `sessionDefaultChannelGroup` with an `EXACT` match on `Organic Search`.
3. **Count sign-ups by first landing page** (Supabase): From whichever store you use, read the table and column (or Stripe metadata key) named in settings. In Supabase or Postgres: `SELECT first_landing_page, count(*) FROM public.profiles WHERE created_at >= '<start>' AND created_at < '<end>' GROUP BY 1` over the same 28 days, limited, when a referrer or `utm_medium` column exists, to rows that came from a search engine. In Stripe: list `GET /v1/customers?created[gte]=<unix start>` and read `metadata[<key>]`. A stored `from` slug or `utm_content` value is mapped to its page with the table in settings.
4. **Join the three on the URL**: Reduce every URL to a path: drop scheme and host, the query string and fragment, a trailing slash (keep `/` for the home page), and lowercase it. Full-join Search Console pages, GA4 landing pages and sign-up pages on that path. Sign-ups with an empty landing page count in the total as unattributed; sign-ups whose page has no organic clicks are listed apart, because they most likely arrived by another channel.
5. **Rank and reconcile**: Work out sign-ups per 100 clicks for every page and for the site. Double down on pages with 3 or more sign-ups at three times the site rate or better; fix pages with 300 or more clicks at under a third of it. Where GA4 key events and database sign-ups for one page differ by more than 30%, say so: consent banners and events firing on the wrong page both show up here.
6. **Post the table**: Post an update card: the summary names how much of the click volume the top pages hold against their sign-ups, then a table of the best converters and the biggest underperformers, and one callout with the first link or page change to make.
## Route B: the browser
### What you need
- Signed in to Search Console, GA4 and your database console (or Stripe) with read access to each.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://search.google.com/search-console/performance/search-analytics. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Export clicks by page**: Pick the property, set Date to `Last 28 days`, open the Pages tab and click Export > Download CSV. Read clicks per page from the file.
2. **Read organic sessions by landing page**: Open `https://analytics.google.com/`, choose the property, go to Reports > Engagement > Landing page and set the same 28 days. Click Add filter, choose Session default channel group, exactly matches `Organic Search`, and apply. Note Sessions and Key events per landing page, and export the table if it runs past one screen.
3. **Count sign-ups by landing page**: In your database console (for Supabase, the SQL Editor at `https://supabase.com/dashboard`), run the count of sign-ups by first landing page for the same dates. If sign-ups live only in Stripe, use Customers > Export with the created date range and check that the metadata key is among the exported columns.
4. **Match the pages**: Reduce every URL to a lowercase path with no query string, fragment or trailing slash, and line the three lists up on it. Count sign-ups without a page as unattributed.
5. **Rank and report**: Work out sign-ups per 100 clicks. Pages with 3 or more sign-ups at three times the site rate are the ones to push; pages with 300 or more clicks under a third of the site rate are the ones to fix.
Note: GA4 report tables and Search Console exports both stop at a limit (Search Console at 1,000 rows), so on large sites the browser route covers the top pages only. A Stripe export depends on the metadata key being offered as a column; the database route is more reliable.
## The report
- **Click share against sign-ups**: how much organic traffic the top pages hold and how many sign-ups they produced.
- **Pages to double down on**: clicks, sessions, sign-ups and sign-ups per 100 clicks.
- **Pages to fix**: high-traffic pages that rarely lead to a sign-up.
- **Unattributed sign-ups** and any page where GA4 and the database disagree.
- The first move, usually a link from a busy page to a page that converts.
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "google-search-console-pages-that-earn-signups/<date of this run, e.g. 2026-10-16>",
"item_key": "google-search-console-pages-that-earn-signups",
"project": "example.com",
"source": "Pages that earn sign-ups: Search Console, GA4 and your sign-ups skill",
"kind": "update",
"title": "3 pages with 40 clicks brought 9 sign-ups; the top 12 brought 2",
"summary": "**12 pages** hold 60% of organic clicks (5,640 of 9,400) and produced **2 sign-ups** in 28 days. **3 pages** with 40 clicks between them produced **9**.\n\n31 organic sign-ups in total; 6 had no stored landing page.",
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Landing page",
"Clicks",
"Sessions",
"Sign-ups",
"Per 100 clicks"
],
"align": [
"left",
"right",
"right",
"right",
"right"
],
"rows": [
[
"/compare/acme-vs-ledgerly",
18,
21,
4,
22.2
],
[
"/integrations/quickbooks",
14,
16,
3,
21.4
],
[
"/templates/net-30-invoice",
8,
11,
2,
25
],
[
"/blog/invoice-template",
1920,
2110,
1,
0.05
],
[
"/blog/what-is-net-30",
1180,
1240,
1,
0.08
],
[
"/glossary/proforma-invoice",
640,
702,
0,
0
],
[
"9 more top-12 pages",
1900,
2050,
0,
0
]
]
}
},
{
"type": "Callout",
"props": {
"text": "First: link /blog/invoice-template (1,920 clicks, 1 sign-up) to /compare/acme-vs-ledgerly, which converts 22 of every 100 clicks.",
"tone": "info"
}
}
]
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
## Run it on a schedule
Suggested: Mondays at 9:00 (`0 9 * * 1`). From cron, with the agent signed in on that machine:
```bash
0 9 * * 1 claude -p "Run the google-search-console-pages-that-earn-signups skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/google-search-console-pages-that-earn-signups#schedule
Source: https://briefkin.dev/skills/google-search-console-pages-that-earn-signups
What your agent needs
Sign-ups must record where the person first landed: a column such as first_landing_page in your database, or a Stripe customer metadata key, filled from the landing URL or a from or utm_content value at sign-up.
Read access to the Search Console property and Viewer access to the GA4 property, through MCP servers or a service account with GOOGLE_APPLICATION_CREDENTIALS.
Read-only access to the sign-up store: the Supabase MCP server with read_only=true, a read-only Postgres role in DATABASE_URL, or a restricted Stripe key with Read on Customers.
Connecting Google Search Console
MCP server. Google doesn't publish one for Search Console. Community MCP servers exist; check which scopes one asks for before you sign in.
API with a service account. Enable the Google Search Console API in a Google Cloud project, create a service account, and add its email under Settings > Users and permissions on the property. Point GOOGLE_APPLICATION_CREDENTIALS at the key and set GSC_SITE_URL to sc-domain:example.com or https://www.example.com/.
OAuth as yourself. When you can't add users to the property, a desktop OAuth client with the webmasters.readonly scope and a stored refresh token works for scheduled runs.
Ask for webmasters.readonly and add the service account as a Restricted user; the reports only read.
Connecting Google Analytics
Google Analytics MCP server, Google's own open-source server (analytics-mcp on PyPI, source). It runs locally with pipx run analytics-mcp, offers run_report, run_realtime_report and get_account_summaries, and Google labels it experimental.
Data API with a service account. Enable the Google Analytics Data API in a Google Cloud project, create a service account, add its email as a Viewer on the GA4 property, and point GOOGLE_APPLICATION_CREDENTIALS at its JSON key. The skill also reads GA4_PROPERTY_ID.
Your own Google login.gcloud auth application-default login --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform --client-id-file=client.json gives the MCP server or a script your user's access without a key file.
Request only analytics.readonly and keep the service account at Viewer: none of these reports needs Editor.
Connecting Supabase
Supabase MCP server, Supabase's official remote server at https://mcp.supabase.com/mcp, signed in with OAuth. URL options narrow it: project_ref=<ref> limits it to one project, read_only=true runs every query as a read-only Postgres user, and features=database,debugging,docs loads only those tool groups. In Claude Code: claude mcp add --scope project --transport http supabase "https://mcp.supabase.com/mcp?project_ref=<ref>&read_only=true", then /mcp and Authenticate. In Codex, add the URL under [mcp_servers.supabase] in ~/.codex/config.toml and run codex mcp login supabase; in Cursor, put it under mcpServers in .cursor/mcp.json. Supabase is also in Claude's connector directory (docs).
Unattended runs with a token. For CI or scheduled jobs, create a personal access token, store it in SUPABASE_ACCESS_TOKEN, and send it as Authorization: Bearer to the same project-scoped, read-only URL.
Direct Postgres or the CLI. Connect with a read-only Postgres role and the connection string from the project's Connect panel, stored in DATABASE_URL, or run supabase link --project-ref <ref> and then the supabase inspect db commands.
Supabase's own advice for unattended routines: scope the server to one project, set read_only=true, limit features, and keep production out of reach unless the report needs it.
Connecting PostgreSQL
MCP server. The PostgreSQL project doesn't publish an MCP server, and the reference Postgres server from the MCP project has been archived. A common community choice is Postgres MCP Pro (postgres-mcp), whose --access-mode=restricted runs every statement in a read-only transaction with a time limit: claude mcp add --env DATABASE_URI=postgresql://report_reader:<password>@db.example.com:5432/app --transport stdio postgres -- uvx postgres-mcp --access-mode=restricted. In Codex and Cursor, put the same command, arguments and DATABASE_URI in the server config. Managed hosts such as Supabase and Neon run their own MCP servers.
psql with a connection string. Claude Code and Codex can run psql "$DATABASE_URL" -X -A -F ',' -c "SELECT ..." directly; it needs only the client and the URL.
A read-only role, used by both: CREATE ROLE report_reader LOGIN PASSWORD '...', GRANT CONNECT ON DATABASE app TO report_reader, then GRANT pg_read_all_data, pg_monitor TO report_reader on Postgres 14 and later (on older versions, grant USAGE on the schema and SELECT on its tables). Add ALTER ROLE report_reader SET default_transaction_read_only = on and ALTER ROLE report_reader SET statement_timeout = '30s'.
The role is the safeguard that holds whichever tool connects: with only read privileges it can't write, alter or drop anything, pg_monitor lets it see the statistics views, and the timeout stops a runaway query. Point it at a read replica if you have one.
Connecting Stripe
Stripe MCP server, Stripe's official remote server at https://mcp.stripe.com. Add Stripe from Claude's connector directory, or run claude mcp add --transport http stripe https://mcp.stripe.com/, and sign in with OAuth; clients without OAuth can send a restricted key as a Bearer token.
The Stripe CLI. Run stripe login once, then stripe subscriptions list --status active --limit 100 or stripe invoices list --status open. The CLI defaults to test mode, so add --live for real data.
API with a restricted key. Create a restricted key (rk_live_...) in the Dashboard under Developers > API keys, store it in STRIPE_API_KEY, and give it Read on the resources the report touches: Subscriptions, Invoices, Charges, Customers, PaymentIntents, Balance and Disputes.
Never hand an agent the secret key (sk_live_...); a restricted key with Read where needed and None everywhere else can't refund, cancel or charge anything.
Questions
Why do GA4 sessions not match Search Console clicks for the same page?
Search Console counts clicks from Google only; GA4's Organic Search channel also includes Bing, DuckDuckGo and others, and it loses visitors who decline analytics cookies. Expect sessions a bit above or below clicks. The skill ranks pages by sign-ups per 100 clicks, so the GA4 gap does not change the order.
What if my sign-ups do not store a landing page?
Then the join has nothing to match, and every sign-up lands in unattributed. Save the first page a visitor lands on in a cookie, or pass it as a from parameter on your sign-up links, and write it to a column or to Stripe customer metadata when the account is created; from the next run on, new sign-ups join.
Do I need Briefkin to use the Pages that earn sign-ups: Search Console, GA4 and your sign-ups skill?
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a report card, so you can glance at it instead of asking again.
Which agents can run it?
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Can it work without an API or MCP server, from my browser?
Yes. Switch the data source to "Your browser" and the steps change to the Google Search Console, Google Analytics, Supabase, PostgreSQL and Stripe pages themselves: the agent opens search.google.com/search-console/performance/search-analytics in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
More skills with Google Search Console and Google Analytics
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.