DocsRecipes

Recipes

B2B SaaS product analytics

4 min readReviewed July 2026

This architecture measures a multi-tenant SaaS product where people work inside customer accounts. It answers activation, adoption, and retention questions without confusing ten active teammates with ten active customers.

Define the decisions#

Start with four operating questions:

  1. Which new accounts reach first value?
  2. How long does activation take?
  3. Which key features are adopted by each account?
  4. Which activated accounts return in later weeks?

Assign a product owner to the activation definition. “Activated” should describe a meaningful outcome, not a convenient page view.

Use an account-aware contract#

Create one product_events collection with:

Event When it fires Required properties
account_created Company workspace commits successfully account_id, user_id, plan, source
data_source_connected First valid source is usable account_id, user_id, source_type
dashboard_published Dashboard becomes available to its audience account_id, user_id, dashboard_id
account_activated First time the approved activation rule is met account_id, time_to_activate_seconds
key_feature_used One approved recurring-value action completes account_id, user_id, feature

Example:

{
  "event": "key_feature_used",
  "event_id": "evt_01J...",
  "account_id": "acct_7",
  "user_id": "usr_42",
  "feature": "scheduled_export",
  "plan": "pro",
  "schema_version": 1
}

Send IDs from your application database, not emails or company names. Attach the account in which the action occurred because one user may belong to several accounts.

Emit server-side facts#

Log completion after the authoritative application transaction. A button click does not prove a data source connected or a dashboard published.

Use an outbox in the same database transaction for account_created and account_activated. A worker sends batches to GraphJSON and keeps a stable event_id across retries. Less critical interaction events can use a lighter queue, but delivery failure must remain observable.

Create separate production and non-production workspaces when access and metrics need a hard boundary.

Make activation explicit#

Suppose activation means connecting a source and publishing a dashboard within seven days of account creation. Emit account_activated once when the application first observes that condition.

This gives the team a stable fact, while a SQL funnel remains useful for diagnosing the path:

WITH per_account AS (
  SELECT
    JSONExtractString(json, 'account_id') AS account_id,
    windowFunnel(7 * 24 * 60 * 60)(
      toDateTime(timestamp),
      JSONExtractString(json, 'event') = 'account_created',
      JSONExtractString(json, 'event') = 'data_source_connected',
      JSONExtractString(json, 'event') = 'dashboard_published'
    ) AS step_reached
  FROM product_events
  WHERE toDateTime(timestamp) >= subtractDays(now(), 90)
  GROUP BY account_id
)
SELECT
  countIf(step_reached >= 1) AS created,
  countIf(step_reached >= 2) AS connected,
  countIf(step_reached >= 3) AS published
FROM per_account

The emitted activation event is convenient for operating metrics; the funnel explains where accounts stop.

Build the operating dashboard#

Create these tiles:

  • weekly accounts created
  • weekly unique accounts activated
  • activation rate by signup cohort
  • median and p90 time to activation
  • unique active accounts by key feature
  • weekly retained activated accounts
  • activation by plan and acquisition source

Count unique account_id, not raw events. Keep plan and source definitions event-time consistent. If you need current plan, derive it from explicit subscription state-change events.

Use titles such as “Weekly accounts using scheduled exports,” not “Engagement.”

Add customer-facing analytics safely#

If customers should see their own usage, reuse the durable account_id as an authorization filter. Your server:

  1. authenticates the user
  2. confirms membership in the selected account
  3. chooses an allowlisted report
  4. adds account_id = authorized_account
  5. requests a GraphJSON iframe URL
  6. returns only the URL

Use the production embedded analytics checklist before launch.

Reconcile and govern#

Each day during rollout, compare:

  • account_created with committed account database rows
  • data_source_connected with active connector records
  • dashboard_published with published dashboard records
  • account_activated with an application-side activation calculation

Sample several account journeys end to end. Track missing IDs, unknown feature names, duplicate event IDs, and worker dead letters.

Review the event contract with product, engineering, and customer success when the activation definition changes. Version the event or metric definition rather than silently changing the meaning of an old dashboard.

Launch checklist#

  • Activation describes a customer outcome and has one owner.
  • Every relevant event includes durable account_id.
  • Server completion—not UI intent—emits business facts.
  • Important events use an outbox or durable queue.
  • Metrics count accounts and users intentionally.
  • Activation and source fields have versioned definitions.
  • Embedded reports enforce tenant filters on the server.
  • Database reconciliation passes for a settled interval.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support