Track trial-to-paid conversion by acquisition source

Build the trial-to-paid funnel split by acquisition source. Surface the channel that converts 8x better than the rest.

Published

difficulty intermediate · time to value 3 minutes · tools used 4

Just say this

Build the trial-to-paid funnel split by acquisition source. Which channel converts trials best?

Why this matters

Most acquisition dashboards show traffic. The metric that actually matters is conversion-by-source. Reddit might be 1% of your traffic but 8% of your paid signups; LinkedIn might be 30% of traffic and 2% of paid. Spending $5k on more LinkedIn ads is then provably wrong.

Mixpanel has this view but you set it up once per source and re-build it every quarter. GA4 has UTMs but no funnel UI. Most teams have a Notion doc with stale numbers and a Slack message asking “wait, where do our actual paying customers come from?”

Agentry’s HogQL groups your funnel by acquisition_source in one query and publishes the table as a public dashboard you can stare at every Monday.

What you get

  • A table of acquisition sources × trial-start × paid-conversion × conversion rate
  • The channel with the best conversion rate (not just the most volume)
  • A public dashboard URL — embeddable in the weekly metrics review

Walk through it

You

Build the trial-to-paid funnel split by acquisition source. Which channel converts trials best?

Agent

Let me check your event vocabulary first.

agentry_list_event_names {
  project_id: "default",
  matching: "trial|signup|subscription|paid"
}
{
  "events": [
    { "name": "trial_started", "count": 4280 },
    { "name": "signup_completed", "count": 4310 },
    { "name": "subscription_started", "count": 412 },
    { "name": "subscription_canceled", "count": 31 }
  ]
}
Agent

Good — trial_started and subscription_started are the right edges. The acquisition_source property should already be on trial_started from your UTM capture. Let me build the by-source funnel.

agentry_analytics_query {
  project_id: "default",
  hogql: "WITH trials AS (SELECT distinct_id, properties.acquisition_source AS source, min(timestamp) AS trial_ts FROM events WHERE event = 'trial_started' AND timestamp >= now() - INTERVAL 90 DAY GROUP BY distinct_id, source), paid AS (SELECT distinct_id, min(timestamp) AS paid_ts FROM events WHERE event = 'subscription_started' AND timestamp >= now() - INTERVAL 90 DAY GROUP BY distinct_id) SELECT t.source AS source, count(DISTINCT t.distinct_id) AS trials_count, count(DISTINCT p.distinct_id) AS paid_count, round(count(DISTINCT p.distinct_id) * 100.0 / count(DISTINCT t.distinct_id), 1) AS conv_pct FROM trials t LEFT JOIN paid p ON t.distinct_id = p.distinct_id AND p.paid_ts > t.trial_ts AND p.paid_ts < t.trial_ts + INTERVAL 14 DAY GROUP BY source ORDER BY trials_count DESC"
}
{
  "rows": [
    { "source": "google_organic", "trials_count": 1820, "paid_count": 142, "conv_pct": 7.8 },
    { "source": "linkedin_ads",   "trials_count": 980,  "paid_count": 41,  "conv_pct": 4.2 },
    { "source": "direct",         "trials_count": 720,  "paid_count": 71,  "conv_pct": 9.9 },
    { "source": "reddit",         "trials_count": 142,  "paid_count": 31,  "conv_pct": 21.8 },
    { "source": "twitter",        "trials_count": 312,  "paid_count": 18,  "conv_pct": 5.8 },
    { "source": "producthunt",    "trials_count": 280,  "paid_count": 88,  "conv_pct": 31.4 }
  ]
}
Agent

The headline finding: Product Hunt converts at 31% and Reddit at 22%. Both are ~10% of your trial volume but bring in 30% of paid customers combined. Your biggest channel (Google organic, 1820 trials) only converts at 8%, and LinkedIn ads at 4% are actively losing you money relative to baseline.

Let me publish this as a dashboard so you can revisit weekly.

agentry_publish_query {
  recipe_id: "trial_to_paid_by_source",
  description: "Trial → Paid by Acquisition Source (90d)",
  params: { window_days: 90, conversion_window_days: 14 }
}
{
  "publication_id": "pub_t2p_1",
  "public_url": "https://agentry.sh/q/pub_t2p_1?key=agp_…"
}
Agent

Dashboard live. Top action: figure out what’s special about your Product Hunt traffic and double down. LinkedIn ads should probably be paused or restructured.

The output

Trial → Paid by Acquisition Source (90d, 14-day conversion window)

SOURCE             TRIALS   PAID   CONV%   ARR IMPLIED
producthunt           280     88   31.4%   $26,400  ←
reddit                142     31   21.8%    $9,300  ←
direct                720     71    9.9%   $21,300
google_organic      1,820    142    7.8%   $42,600
twitter               312     18    5.8%    $5,400
linkedin_ads          980     41    4.2%   $12,300  ←  weak

Volume vs quality split:
  Volume leaders:  google_organic, linkedin_ads
  Quality leaders: producthunt, reddit, direct

Actions:
  ★ Double down on Product Hunt — what's working? Find a way to repeat
  ★ Reddit at 22% — increase community presence, do an AMA
  ☆ Pause LinkedIn ads at current targeting — 4.2% is killing ROAS

Public dashboard:
  https://agentry.sh/q/pub_t2p_1?key=agp_xxx

Setting it up

You need two events plus a UTM-style source on the trial event:

// On signup completion (server-side, after email verify):
await fetch(`https://api.agentry.sh/v1/analytics/${PROJECT_ID}/`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTRY_DSN}`,
    "Content-Type": "application/json",
    "User-Agent": "myapp/1.0",  // REQUIRED — Cloudflare 403s default UAs
  },
  body: JSON.stringify({
    event: "trial_started",
    distinct_id: user.email,
    properties: {
      acquisition_source: user.utm_source ?? "direct",  // ← the key field
      acquisition_campaign: user.utm_campaign,
      referrer: user.initial_referrer,
    },
  }),
});

// On Stripe webhook (subscription.created → trial → active OR direct paid):
await fetch(`https://api.agentry.sh/v1/analytics/${PROJECT_ID}/`, {
  method: "POST",
  headers: { /* same */ "User-Agent": "myapp-stripe/1.0" },
  body: JSON.stringify({
    event: "subscription_started",
    distinct_id: stripeCustomerEmail,
    properties: { plan: subscription.items.data[0].plan.nickname, amount: subscription.items.data[0].plan.amount },
  }),
});

To capture utm_source on signup, snapshot it from the landing page into a first-party cookie (or localStorage) and read it on signup:

// On any landing page hit:
const params = new URLSearchParams(window.location.search);
if (params.get("utm_source")) {
  localStorage.setItem("acq_source", params.get("utm_source")!);
  localStorage.setItem("acq_campaign", params.get("utm_campaign") ?? "");
}
// On signup form submission, read back from localStorage and POST with the trial_started event.

Variations

  • “Same funnel but for the last 30 days only — has anything shifted post-launch?”
  • “Compare trial-to-paid by GEO (country) — are non-US trials converting differently?”
  • “For Product Hunt specifically, what’s the path-to-paid? Show the average # of sessions before paying.”
  • “Build a separate dashboard for paid ad channels only (LinkedIn, Google ads, Facebook). Compare CPA × conversion %.”

Try this recipe in your own agent.

Paste the prompt above into your agent. It'll set up Agentry against your data, then run the recipe and show you the real output.

Install https://agentry.sh/agentry.md and set it up