Your CEO asks why marketing reports 412 demo bookings but sales only saw 38 qualified ones. You open GA4, find page_view and an auto-collected form_submit, and realize the funnel has never actually been instrumented. We see this every week.
Out of the box, GA4 tells you which pages got traffic. It does not tell you where prospects fall out of a B2B SaaS funnel, which channel produced paid conversions, or whether a trial user hit activation. To get that, you need a deliberate event taxonomy. Below is the smallest version that still answers the questions a growth team actually has.
The five events every B2B SaaS funnel needs
Forget the 40-event spreadsheets. For most B2B SaaS funnels, five events do 90% of the work:
- lead_captured — any form submission that creates a contactable record (newsletter, gated asset, contact request). Parameters:
method,form_id,content_offer. - demo_booked — calendar event confirmed in Chili Piper, Calendly, or HubSpot Meetings. Parameters:
booking_source,rep_assigned,account_domain. - trial_started — user activated a trial workspace. Parameters:
plan,seat_count,signup_method. - activation_milestone — the one in-product action that correlates with paid conversion (invited a teammate, connected an integration, ran the first report). Parameters:
milestone_name,time_to_activation_sec. - paid_conversion — first successful charge. Parameters:
plan,arr_value,currency,billing_cycle.
Mark all five as key events in GA4 (Admin → Events → Mark as key event). That is what lets them show up in attribution reports and tie back to Google Ads conversions.
How to fire them: GTM trigger plus dataLayer push
Fire events from the application layer, not from CSS selectors. CSS triggers break the next time a developer renames a class. The pattern we use everywhere:
// Fire from your app code when the action actually completes
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'lead_captured',
method: 'demo_form',
form_id: 'enterprise-contact-v3',
content_offer: 'roi-calculator',
user_id: 'usr_8f3a2c',
account_domain: 'acmecorp.com'
});
In Google Tag Manager, create a custom event trigger that listens for the event name (lead_captured), then a GA4 Event tag that maps each dataLayer key to an event parameter. Same pattern for the other four events. One trigger and one tag per event.
For server-side events like paid_conversion from a Stripe webhook, send them via the GA4 Measurement Protocol with the same parameter shape. That keeps the schema consistent whether the event originated client-side or server-side.
How to track them in GA4: custom dimensions and user_id
An event parameter is invisible in standard GA4 reports until you register it as a custom dimension. Go to Admin → Custom definitions and register the parameters you actually want to slice by: plan, milestone_name, booking_source, account_domain. Skip the ones you will never filter on.
Set user_id on every event the moment you know it (after sign-in, after form capture). This is the difference between GA4 stitching a journey across devices and showing you five orphaned sessions. Configure it once in the GA4 configuration tag, then ensure your dataLayer pushes the same ID every time.
For revenue, set the value and currency parameters on paid_conversion so GA4 will report ARR-weighted attribution rather than raw conversion counts. A $40,000 enterprise close should not look the same as a $49 self-serve sign-up.
Where teams typically screw it up
Four mistakes account for most of the broken GA4 setups we audit:
- PII in event parameters. Email addresses, phone numbers, and full names in
user_idor any event param will get your property suspended. Hash the email before sending, or use an internal user ID. - Missing user_id. Without it, you cannot connect a
lead_capturedon the marketing site to atrial_startedin the product. Most funnels are broken at exactly this seam. - Double-firing. A “Thank you” page that fires
demo_bookedon every reload inflates conversions by 20-40%. Fire on the API success callback, not on the redirect. - Untagged paid conversions. If
paid_conversiononly fires client-side after a Stripe redirect, you lose 10-15% of conversions to ad blockers and abandoned tabs. Send it server-side.
None of this is exotic. It is the GA4 setup we ship in the first two weeks of every conversion analytics engagement. The reason most SaaS teams do not have it is that it gets quietly deprioritized when a launch lands or a campaign goes live.
Now what? If you want the larger picture of how conversion data feeds long-term organic growth, read our piece on the SEO plays that compound, or look at how we structure end-to-end measurement on our conversion analytics service page.