—
Tracks how many appointments each ad campaign produced. Use it to allocate spend toward campaigns that book the most intros and discoveries.
Intros Booked — meetings classified as "intro" (or "Investor Success Profile" / "ISP"), counted by booking date (hs_createdate) within the selected range.
Discoveries Booked — meetings classified as "discovery" (HubSpot enum types Discovery Call - Main or Discovery Call - Setters), counted by booking date.
Classification logic matches the setter and closer dashboards exactly, so totals reconcile.
First-touch attribution from the meeting's primary contact:
1. hs_analytics_source_data_2 (Drill Down 2) — the campaign or ad name. Most specific.
2. If empty, fall back to hs_analytics_source_data_1 (Drill Down 1) — the platform (e.g., "Facebook").
3. If both empty, fall back to hs_analytics_source (Original Source) — e.g., "Paid Social", "Direct Traffic".
4. If all three are empty, the row appears as (not set).
Each row's badge tells you which level of attribution it is: DD2 (campaign-specific), DD1 (platform only), or SRC (source only). DD2 rows are the most actionable — you know which specific campaign drove the appointment.
Many leads have empty Drill Down 2 — older contacts, leads from sources without UTM tracking, manual entries. Showing them at the platform/source level keeps the totals honest. They reconcile against the setter and closer dashboards.
The dashboard also treats purely numeric DD2 values as malformed (a defunct integration left HubSpot user IDs stamped on some older contacts) and falls back to DD1 for those. Real campaign names are typically alphabetic or alphanumeric.
Discoveries Booked descending by default; click any number-column header to flip. (not set) rows always pin to the top regardless of sort.
Sorting by % AI: rows with no leads at all (denominator of 0) sink to the bottom regardless of direction. Equal-percentage rows tiebreak by total lead volume so a 100% rate from 50 leads outranks a 100% rate from 1.
Intros / Discoveries pies — top campaigns by booking volume.
New AI Leads pie — top campaigns by accredited-investor lead volume.
% AI / $ per AI Lead bar chart — toggleable between two lenses (top-right buttons in the panel header):
Quality — top campaigns by accredited share (% AI). Highest quality first.
Cost — top campaigns by Cost Per AI Lead (CPAIL), cheapest first. Only includes campaigns that have BOTH Meta spend AND AI leads.
Campaigns with fewer than 10 total leads are too statistically noisy to rank against larger campaigns, so they're rolled up into a single "Low volume" bar pinned to the top of the chart. Same threshold in both modes. Click that bar to open a drawer listing the rolled-up campaigns.
Source filter — multi-select dropdown filtering by hs_analytics_source (Original Source). Affects everything: KPIs, pies, the rate chart, table, and totals strip.
Row filter buttons — All / Booked Appts / Leads. Only affects the table body and totals strip; KPIs and charts stay the same.
Click any totals-strip cell — narrows the table to rows that contributed to that total. Click "Intros" to see only campaigns with intros; "New AI Leads" to see only campaigns with new accredited leads; etc. Click the active cell again to clear back to All.
The top KPIs (Intros / Discoveries) and the totals strip above the table show similar numbers but answer different questions:
Top KPIs — the period's headline numbers. Reflect the source filter (so they show the slice you're looking at) but never the row filter (clicking "Booked Appts" doesn't change them).
Totals strip — the sum of whatever's currently in the table. Reflects source filter and row filter, so it always equals the column sums of the visible rows.
The main table, the AI pie, the rate / cost bar chart, and the totals strip all use Total AI Leads as the headline AI metric. Total AI = New AI + Returning AI, both event-counted (exact). No estimates anywhere.
Click any campaign row in the table to open a per-campaign drill drawer with the full breakdown: Total Leads / New Leads / Returning Leads, then Total AI / New AI / Returning AI — six metrics with Count, % of Total Leads, and CPL each.
Three pills above the totals strip — Total / New / Returning — reshape the four lead-related cells (Leads, AI count, % AI, CPAIL) without leaving the page. Intros / Discoveries / Spend stay constant across views.
Total view shows Total Leads, Total AI, % Total AI, CPAIL on Total AI.
New view shows New Leads, New AI, % New AI, CPAIL on New AI. Counts contacts created in the period.
Returning view shows Returning Leads, Returning AI, % Returning AI, CPAIL on Returning AI. Counts contacts whose createdate is before the period but who submitted a form during the period. Attribution comes from their last in-period form submission — last touch within the window. (Page views alone don't count toward "returning" — that signal is too weak and the fetch volume is too high.)
Cells stay clickable for table drilling in every view, but the drill target updates per view: clicking the AI cell in New view filters to rows with New AI > 0; in Returning view, to rows with Returning AI > 0. Switching lens resets the row filter to "All" so the table doesn't get stuck in a stale state.
The Spend column is fetched directly from the Meta Marketing API per Meta campaign, summed across the active date range. v10.6 joins on Meta campaign ID (hsa_cam parsed from the contact's attribution event), not on campaign name — eliminates the URL-encoding asymmetry between "(a+)" and "(a )" that earlier versions had to work around.
Rows that aren't Meta campaigns — Google or LinkedIn campaigns, "(not set)", platform-fallback rows (DD1 / SRC) — show "—" in Spend (and consequently "—" in CPAIL).
Spend is always exact — Meta's reported spend, no derivation.
Google Ads and LinkedIn Marketing API integrations are pending — those campaigns will continue showing "—" in Spend until added.
The CPAIL column = Spend ÷ Total AI Leads. Now exact across the board because Total AI is exact (New AI + Returning AI, both event-counted). Null (shown as "—") when the row has no spend or zero Total AI.
The aggregate CPAIL in the totals strip = sum(Spend across visible Meta rows) ÷ sum(Total AI across those same rows). Numerator and denominator stay at the same scope so the aggregate is honest.
The orphan panel below the campaign table lists Meta campaigns that spent money in this period but have no contact attributed to them via events. The number alongside each campaign is its Meta spend over the active date range.
This is computed independently of the source filter — orphans by definition have no HubSpot record, so source slicing doesn't apply. The panel only appears when there are orphans.
v10.6 detection. An orphan now means "a Meta campaign ID with spend that no contact in the period attributed to via their visit/submission events." Stricter and cleaner than v10.5's name-based detection: there's no fuzzy matching to fail.
Common causes (worth investigating, not necessarily failures): brand-new campaigns where leads are still arriving (especially with longer attribution windows), campaigns where impressions didn't convert, or campaigns whose URL metadata didn't reach HubSpot's events stream.
Where source attribution comes from. v10.5 read each contact's attribution from hs_analytics_source* contact properties. Those properties drift via last-write-wins — every new touchpoint can rewrite them. v10.6 reads attribution from the contact's actual events instead: hs_visit_source, hs_visit_source_details_1, and hs_visit_source_details_2 on each event are immutable timestamped values. We use them directly.
Which event we read. For new contacts (createdate in period): the earliest e_submitted_form event with source fields — that's the contact's creation moment. For returning contacts (createdate before period, form submission in period): the latest in-period form submission — last touch within the window.
Why form submissions, not page views. v10.6.1 dropped e_visited_page from the fetch — page views fire for every anonymous visit across the entire portal, hitting tens of thousands of events that we'd then filter down to a few thousand we care about. Form submissions are orders of magnitude rarer and carry the same source data via hsa_cam, hsa_net, and UTM parameters in the URL. Lead Ads work identically: the source data lives in the form-submission event's URL even though there's no page view.
Fallback. When a contact has no form submission with source fields (manually-created in CRM, imported from a list, or tracking gaps), we fall back to their hs_analytics_source* contact properties — same logic v10.5 used. Documented as a fallback so manually-created contacts still appear, but they're a small fraction.
The Guide, the Low Volume drawer, and the per-campaign drill drawer all close on Esc, on the × button, or by clicking outside the drawer.
Meeting attribution from events. Intros and Discoveries still resolve attribution via contact properties (CONTACT_ATTRIB), not events. Meeting-having contacts can have createdates from years ago, so fetching their full event histories is a significantly bigger scope. Deferred to v10.7+.
Funded investments — deferred. Investment attribution should arguably use the Closed Won milestone-stamped property (traffic_source_2_before_closed_won) rather than first-touch, which is a separate design decision.
Cache — this dashboard fetches live from HubSpot every load (no R2 cache). Loads run 60–120 seconds for "Last 12 months."