# Compare self-serve vs sales-led user lifecycles

> Split your funnel by how the user arrived — pricing-page vs demo. Compare activation, retention, expansion side by side.

## Agent adaptation contract

- Canonical human page: https://agentry.sh/workflows/self-serve-vs-sales-led
- Execution mode: on_demand
- Immutable automation template: none
- Applies to: b2b-saas, enterprise-sales
- Required example events: signup_completed, demo_completed, first_project_created, subscription_started, plan_changed
- Required Agentry resources: none declared
- Do not use when:
  - Do not use until the example events are mapped to observed project signals, the current onboarding state is verified, and live event/property reads prove the required data is present.
  - Do not recommend this when the product has no stable account or workspace identity.
- Ask before using:
  - Which observed events map to signup_completed, demo_completed, first_project_created, subscription_started, plan_changed? Is the current onboarding state verified, and do live event/property reads show non-synthetic traffic for them?
  - Which live properties provide signup_completed.signup_path, demo_completed.account_id, plan_changed.from_plan_rank, plan_changed.to_plan_rank, and which stable user or account identifier joins the signals?

This is an adaptable workflow example, not an API recipe. Map event and property names to the project's saved signal map, require status: "verified" from GET /v1/projects/:project_id/onboarding, and confirm the required signals through live event/property metadata and rows. Fetch current OpenAPI or query-blueprint details before making calls. Do not infer unattended authority from this page.

## Why this matters

Averaging across go-to-market motions hides everything. Self-serve and sales-led customers behave nothing alike:

- Self-serve activates in days, churns fast, has tiny ACV, compounds via word-of-mouth.
- Sales-led activates in weeks, churns slowly, has 30x ACV, requires a CSM.

A blended "30-day activation rate" tells you nothing useful about either. The PM looks at the number, sees 25%, doesn't know whether to ship a self-serve onboarding fix or hire another CSM. Most analytics views never split the cohorts because nobody wired up the source signal at signup.

Agentry's HogQL splits cohorts by source in one query and shows the two ICPs side by side. The strategic conversation gets unblocked.

## What you get

- Two cohorts (self-serve, sales-led) with their 30/60/90-day rates for activation, retention, upgrade
- The metric where the gap is largest (= where the GTM workflow should differ most)
- A protected metric report you can share with the leadership team after the source signal is verified

## Walk through it

**You:**
> Split signups into self-serve (signed up via /pricing) vs sales-led (signed up after a demo). Compare their 30/60/90-day activation, retention, expansion.

**Agent:**
> Building the two cohorts first.

```text
Endpoint: POST /v1/projects/:project_id/cohorts
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fcohorts&method=post
Concept fields:
  name: "self_serve_q1",
  description: "Signups that came through /pricing (no preceding demo event)",
  filters: [
    { event: "signup_completed", properties: { signup_path: "/pricing" }, since: "90d" }
  ]
```
```text
{ "cohort_id": "coh_ss_1", "member_count": 1840 }
```

```text
Endpoint: POST /v1/projects/:project_id/cohorts
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fcohorts&method=post
Concept fields:
  name: "sales_led_q1",
  description: "Signups that completed a demo before signing up (within 30d)",
  filters: [
    { event: "demo_completed", since: "120d" },
    { event: "signup_completed", since: "90d" }
  ]
```
```text
{ "cohort_id": "coh_sl_1", "member_count": 168 }
```

**Agent:**
> 1,840 self-serve vs 168 sales-led. Now run the comparison.

```text
Endpoint: POST /v1/projects/:project_id/analytics/query
Exact shape: /v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fanalytics%2Fquery&method=post
Custom HogQL goes in the OpenAPI-defined `query` field.
Concept fields:
  query: "WITH self_serve AS (SELECT distinct_id, min(timestamp) AS signup_ts FROM events WHERE event = 'signup_completed' AND properties.signup_path = '/pricing' AND timestamp >= now() - INTERVAL 90 DAY GROUP BY distinct_id), sales_led AS (SELECT distinct_id, min(timestamp) AS signup_ts FROM events WHERE event = 'signup_completed' AND distinct_id IN (SELECT distinct_id FROM events WHERE event = 'demo_completed' AND timestamp >= now() - INTERVAL 120 DAY) GROUP BY distinct_id), metrics AS (SELECT 'self_serve' AS cohort, count(DISTINCT s.distinct_id) AS n, countIf(DISTINCT s.distinct_id, exists(SELECT 1 FROM events e WHERE e.distinct_id = s.distinct_id AND e.event = 'first_project_created' AND e.timestamp < s.signup_ts + INTERVAL 30 DAY)) AS activated_30d, countIf(DISTINCT s.distinct_id, exists(SELECT 1 FROM events e WHERE e.distinct_id = s.distinct_id AND e.event = 'subscription_started' AND e.timestamp < s.signup_ts + INTERVAL 60 DAY)) AS paid_60d, countIf(DISTINCT s.distinct_id, exists(SELECT 1 FROM events e WHERE e.distinct_id = s.distinct_id AND e.event = 'plan_changed' AND e.properties.to_plan_rank > e.properties.from_plan_rank AND e.timestamp < s.signup_ts + INTERVAL 90 DAY)) AS expanded_90d FROM self_serve s UNION ALL SELECT 'sales_led', count(DISTINCT s.distinct_id), countIf(...), countIf(...), countIf(...) FROM sales_led s) SELECT * FROM metrics"
```
```text
{
  "rows": [
    { "cohort": "self_serve", "n": 1840, "activated_30d": 920, "paid_60d": 184, "expanded_90d": 22 },
    { "cohort": "sales_led",  "n": 168,  "activated_30d": 134, "paid_60d": 134, "expanded_90d": 41 }
  ]
}
```

**Agent:**
> The contrast is dramatic:
>
> |              | self-serve | sales-led |
> |--------------|-----------:|----------:|
> | n            | 1,840      | 168       |
> | activated 30d| 50%        | 80%       |
> | paid 60d     | 10%        | 80%       |
> | expanded 90d | 1.2%       | 24%       |
>
> Volume is 11x on self-serve. Conversion is 8x better on sales-led. Expansion is 20x better on sales-led.
>
> **Strategic read:** self-serve is correctly funded if your ACP is low and you want compounding. Sales-led is correctly funded if you can run a sales motion with that conversion. The MIDDLE — generic onboarding that treats them the same — is wrong for both.
>
> Specifically: your sales-led activation gap (50→80%) is a sales-engineering job, not a product job. Self-serve's pay-conversion gap (10% vs 80%) is the standard PLG funnel improvement, worth investing.

## The output

```
Self-Serve vs Sales-Led — last 90d cohort

METRIC              SELF-SERVE    SALES-LED   RATIO   COMMENTARY
n (signups)              1,840          168    11.0x  Volume lives in self-serve
activated 30d              50%          80%   0.62x  Sales-led activates better
paid 60d                   10%          80%   0.13x  Sales-led converts 8x better
expanded 90d              1.2%          24%   0.05x  Sales-led expands 20x better

WHERE THE WORKFLOWS DIVERGE:
  Self-serve activation:  50% → improve via in-product onboarding
  Self-serve conversion:  10% → improve via trial UX, pricing page
  Sales-led activation:   80% → already strong, hold the line
  Sales-led conversion:   80% → CSM quality issue, hire to match deal flow

IMPLIED ARR (rough, assuming self-serve $50/mo, sales-led $1,500/mo):
  Self-serve 60d revenue:    184 × $50    = $9,200/mo new
  Sales-led 60d revenue:     134 × $1,500 = $201,000/mo new
  → Sales-led drives 95% of revenue from 8% of accounts.

Strategic question: are we under-investing in sales given that ratio?
```

## Setting it up

The only new requirement is **knowing how each signup arrived**. Capture it on the signup event:

```ts
// On signup completion, attach the path that brought them
await fetch(`https://api.agentry.sh/v1/analytics/`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTRY_SERVER_API_KEY}`,
    "Content-Type": "application/json",
    "User-Agent": "myapp/1.0",  // REQUIRED — Cloudflare 403s default UAs
  },
  body: JSON.stringify({
    event: "signup_completed",
    distinct_id: user.email,
    properties: {
      signup_path: user.signup_path,  // "/pricing" | "/demo" | "/start" | etc.
      had_demo: user.last_demo_at != null,
      utm_source: user.utm_source,
    },
  }),
});

// Fire demo_completed from your demo-booking tool's webhook (Calendly, etc.)
await fetch(`https://api.agentry.sh/v1/analytics/`, {
  method: "POST",
  headers: { /* same */ "User-Agent": "myapp-calendly/1.0" },
  body: JSON.stringify({
    event: "demo_completed",
    distinct_id: bookingEmail,  // ← MUST match what they sign up with
    properties: { ae: bookingAssignee, demo_outcome: "qualified" },
  }),
});
```

If you can't reliably fire `demo_completed`, an OK fallback is the signup-path heuristic alone (`/pricing` = self-serve, `/contact-sales` = sales-led).

## Variations

- *"Same split but for last quarter only — has the ratio changed since we added the AE team?"*
- *"Build the same comparison for 3 sub-cohorts: low ACV self-serve, mid-market sales-led, enterprise sales-led."*
- *"For sales-led only, compare activation rate by AE. Which reps are best at handoff?"*
- *"For self-serve only, find users who didn't activate in 30d but came back at 60d and DID activate. What did they do differently?"*
