Skip to content
Telemetry
Product analytics SQL recipe

Build a signup-to-activation funnel in SQL

Calculate unique-user conversion through signup, onboarding, integration, and first-value milestones.

Intermediateproduct_eventsReviewed 2026-07-27Tested with Apache DataFusion 45.2.0

Reviewed by the Telemetry product team on . We checked the SQL syntax, required event fields, sample results, and limits on using the query. Who reviews this page

Question answered

Where do new users leave before reaching first value?

A funnel is only useful when every step has a behavioral definition. This query creates one row per user, enforces milestone order, and calculates conversion from signup and from the previous step.

Event schema

Fields the query expects

FieldTypeWhy it exists
timestamp_utcTimestampWhen the product event occurred.
user_idUtf8Stable user identifier.
event_nameUtf8Stable snake_case milestone name.
sourceUtf8Acquisition or campaign source.
DataFusion SQL

Copy the query

sql
WITH events AS (
  SELECT user_id, event_name, timestamp_utc
  FROM product_events
  WHERE timestamp_utc >= now() - INTERVAL '30 days'
),
signups AS (
  SELECT user_id, MIN(timestamp_utc) AS signed_up_at
  FROM events WHERE event_name = 'signup_completed'
  GROUP BY user_id
),
onboarding AS (
  SELECT s.user_id, s.signed_up_at, MIN(e.timestamp_utc) AS onboarded_at
  FROM signups AS s
  LEFT JOIN events AS e ON e.user_id = s.user_id
    AND e.event_name = 'onboarding_completed'
    AND e.timestamp_utc >= s.signed_up_at
  GROUP BY s.user_id, s.signed_up_at
),
integrations AS (
  SELECT o.user_id, o.signed_up_at, o.onboarded_at,
    MIN(e.timestamp_utc) AS integrated_at
  FROM onboarding AS o
  LEFT JOIN events AS e ON e.user_id = o.user_id
    AND e.event_name = 'integration_connected'
    AND e.timestamp_utc >= o.onboarded_at
  GROUP BY o.user_id, o.signed_up_at, o.onboarded_at
),
user_funnel AS (
  SELECT i.user_id, i.signed_up_at, i.onboarded_at, i.integrated_at,
    MIN(e.timestamp_utc) AS activated_at
  FROM integrations AS i
  LEFT JOIN events AS e ON e.user_id = i.user_id
    AND e.event_name = 'first_value_completed'
    AND e.timestamp_utc >= i.integrated_at
  GROUP BY i.user_id, i.signed_up_at, i.onboarded_at, i.integrated_at
),
stage_counts AS (
  SELECT 1 AS step_order, 'Signed up' AS step, COUNT(signed_up_at) AS users
  FROM user_funnel
  UNION ALL
  SELECT 2, 'Onboarded', COUNT(onboarded_at) FROM user_funnel
  UNION ALL
  SELECT 3, 'Integrated', COUNT(integrated_at) FROM user_funnel
  UNION ALL
  SELECT 4, 'Activated', COUNT(activated_at) FROM user_funnel
)
SELECT
  step_order,
  step,
  users,
  100.0 * users
    / NULLIF(MAX(CASE WHEN step_order = 1 THEN users END) OVER (), 0)
    AS conversion_from_signup_pct,
  100.0 * users
    / NULLIF(LAG(users) OVER (ORDER BY step_order), 0)
    AS conversion_from_previous_pct
FROM stage_counts
ORDER BY step_order;

This read-only query is planned and executed against an empty typed table with Apache DataFusion 45.2.0. We review the synthetic sample output separately. Check field types, thresholds, and counting rules against your own data. Read the testing methodology.

Query result

Signup-to-activation funnel

The largest absolute loss occurs before onboarding completion, while integration remains the strongest later bottleneck.

step_orderstepusersconversion_from_signup_pctconversion_from_previous_pct
1Signed up8100—
2Onboarded67575
3Integrated45066.67
4Activated337.575

Synthetic example output. Run the query against your own event schema and thresholds before using it for operational decisions.

Signup-to-activation funnel: static chart of synthetic users values from the Build a signup-to-activation funnel in SQL example result
Download this SVG chart of the sample results for an article, runbook, or design review. Please credit Telemetry.

Reproduce the example

Download the sample data

The JSON bundle includes the event schema with field types, reproducible input rows, exact SQL, expected output, review notes, and engine version. The CSV contains the displayed result.

How the SQL works

  1. 1Each CTE selects the first milestone at or after the previous eligible stage, retaining one row per signed-up user.
  2. 2A missing or out-of-order milestone blocks all later steps. A later valid retry can advance the user, and duplicates count once.
  3. 3The query reports both conversion from signup and conversion from the previous step, making total performance and the largest transition loss visible together.
  4. 4The event names should represent completed milestones, not page views or button clicks that do not prove the user achieved the step.

Edge cases to check

  • Use a cohort window based on signup time and allow enough time for recent signups to activate.
  • Events that arrive late can temporarily move users backward in a recently calculated funnel.
  • Segment by acquisition source only after the total funnel has enough volume.

Recommended dashboard

  • Funnel bars: users by step
  • Line chart: activation rate by signup week
  • Table: conversion by source, plan, or persona

Alert guidance

Use a low-urgency alert when a signup cohort's observation window has closed and activation falls below your chosen baseline threshold.

Read alert setup

Set up the events this query needs

Related instrumentation and guides

Define the source data

Event schemas for this analysis

Continue the analysis

Run it on your events

Create a table, adapt the fields, and save the result

Start free, send structured events, and use the query result as a chart, shared dashboard widget, or alert input.

Get an API key