This is the question a dedicated SEO tool cannot answer, because the answer lives in three systems: what people searched for (Search Console), what they did on the site (GA4), and what they paid or became (HubSpot, Stripe). Joining them is what makes an SEO number a business number.
Steps
-
Pick the revenue path. Self-serve or ecommerce: revenue is in GA4 (path A). Sales-led B2B: pipeline is in HubSpot (path B). Both can be true; run both and keep them separate. Ask which applies if it is not obvious.
-
Pull the organic side.
google_search_console.querywithdimensions: ["page"]over 90 days ending 3 days ago,rowLimit: 500, and again withdimensions: ["query", "page"],rowLimit: 5000. Ninety days, because the path from search to money is longer than the path from search to click. -
Path A: revenue in GA4.
google_analytics.run_reportwithdimensions: ["landingPage"],metrics: ["sessions", "conversions", "totalRevenue", "ecommercePurchases"], the same dates,dimensionFilteronsessionMediumEXACTorganic. Join to Search Console on the normalised path. Compute revenue per organic click per page. -
Path B: pipeline in HubSpot. First
hubspot.list_propertiesfor contacts and for deals to find the analytics source and first-page fields. They are usuallyhs_analytics_source,hs_analytics_first_urlandhs_analytics_source_data_1, but accounts differ; do not hardcode. Then:hubspot.search_contactswith afilterGroupsentry for the source propertyEQORGANIC_SEARCHandcreatedatewithin the window, requestingpropertiesfor email, create date and first URL. Group contacts by the path of their first URL and join to Search Console pages. This gives leads per organic page.hubspot.list_pipelinesfor deals to learn which stage ids mean closed won. Thenhubspot.search_dealsfiltered to the organic source and closed won within the window, requesting amount, close date and the first-URL property if deals carry one. This gives revenue from organic at the account level even when deal-to-page attribution is missing. Page through withafteruntil exhausted; a partial pull silently understates everything.
-
Stripe, only if attribution was captured. Stripe has no idea where a customer came from unless the checkout stored it. Ask whether UTM or referrer values are written to customer or subscription metadata. If they are,
stripe.searchwith a metadata query on the organic value gives revenue directly; if not, skip this step and say so rather than guessing. -
Attribute queries. For the top revenue pages, take their query rows from step 2 and report the queries as the demand behind the revenue. This is query-to-page-to-revenue, not query-to-revenue; keep that distinction in the write-up.
-
Report. Top ten organic pages by revenue or pipeline: page, clicks, sessions, conversions or contacts, revenue or closed-won amount, revenue per click, and the top three queries behind it. Then the share of total revenue or pipeline that organic accounts for, pages with real traffic and no revenue, and the one or two queries most worth defending. Keep GA4 revenue and HubSpot revenue in separate columns; never add them.
Gotchas
- Different systems attribute differently. HubSpot's source is first touch by its own tracking cookie; GA4's is session-scoped and defaults to last non-direct click. Present each as what it is. Adding them double counts.
- GA4 revenue requires ecommerce events. If
totalRevenueis zero everywhere, the site is not sending purchase events; that is a tracking gap, not zero revenue. - HubSpot's ORGANIC_SEARCH is HubSpot's classification, based on referrer, not on Search Console. It includes all search engines and misses visitors whose referrer was stripped.
- Sales cycles outlast the window. A deal closed this quarter came from a search last quarter. Use the contact's create date for lead attribution and the close date for revenue, and say the windows differ.
- Stripe amounts are in minor units (cents). Divide by 100 once, and report per currency.
- Join on the path. Strip origin, query string and trailing slash from Search Console, GA4 and HubSpot URLs before joining, and report the unmatched share.