Docs
Guides

Activation, churn, and expansion

Turn observed product behavior into explicit Customer Success outcomes.

Kite does not impose a universal activation, churn, or revenue model. Combine its primitives to encode outcomes specific to your product.

QuestionPrimitive
What ordered path reaches first value?Journey
What mutually exclusive stage is true now?Lifecycle
Which customers belong in a working queue?Segment
What action follows a trigger and condition?Rule
What continuous summary describes behavior?Health

Model activation

Activation should represent durable first value, not account creation or login. Use a journey when first value requires ordered milestones.

journeys/activation.ts
import { defineJourney } from '@kitesdk/config';

export default defineJourney({
  id: 'activation',
  name: 'Activation',
  startsWhen: { event: 'account.created' },
  expiresAfter: '30d',
  steps: [
    {
      id: 'connected',
      name: 'Connected data',
      completedWhen: { event: 'integration.connected' },
      expectedWithin: '3d',
    },
    {
      id: 'first_value',
      name: 'Reached first value',
      completedWhen: { event: 'report.exported' },
      expectedWithin: '7d',
    },
  ],
  onComplete: { emit: 'customer.activated' },
  onExpire: { emit: 'customer.activation_expired' },
});

Move the customer into an activated lifecycle state when the journey completes:

states/lifecycle.ts
import { defineLifecycle } from '@kitesdk/config';

export default defineLifecycle({
  initialState: 'new',
  states: {
    new: {},
    trial: { enteredWhen: { event: 'account.created' } },
    activated: {
      description: 'Reached the product first-value outcome',
      enteredWhen: { journey: 'activation', status: 'complete' },
    },
  },
  emits: { onEnter: { activated: 'customer.lifecycle.activated' } },
});

Required steps complete in order; optional steps can complete opportunistically. expectedWithin is descriptive and does not trigger an alert. expiresAfter is evaluated by expiration processing, and complete or expired journey instances do not restart.

Emitted names become internal timeline events. They reach an external receiver only when a matching webhook is configured.

Model churn risk

Use a segment for a non-exclusive working queue. Use lifecycle only when risk should replace the customer's current stage.

segments/at-risk.ts
import { defineSegments } from '@kitesdk/config';

export default defineSegments([
  {
    id: 'at_risk',
    name: 'At risk',
    filter: {
      any: [
        { inactiveDays: { min: 14 } },
        { healthScore: { max: 40 } },
        { eventCount: { event: 'api.error', days: 7, min: 5 } },
      ],
    },
    onEnter: { emit: 'customer.churn_risk.detected' },
    onExit: { emit: 'customer.churn_risk.resolved' },
  },
]);

This models risk, not confirmed churn. Record cancellation, non-renewal, and contraction as authoritative billing or CRM events.

Time conditions do not wake themselves up. Inactivity and account-age filters are reevaluated when events are processed or during recomputation. A scheduled health refresh can also cause a segment that depends on health to be reevaluated.

Model expansion

Expansion qualification is usually a segment because it is a current, non-exclusive cohort. featuresUnused is supported by the segment engine and reads the current plan, availableFeatures, and usedFeatures traits.

segments/expansion.ts
import { defineSegments } from '@kitesdk/config';

export default defineSegments([
  {
    id: 'expansion_candidate',
    name: 'Expansion candidate',
    filter: {
      all: [
        { lifecycle: 'activated' },
        { healthScore: { min: 75 } },
        { featuresUnused: { plan: 'pro', minCount: 1 } },
      ],
    },
    onEnter: { emit: 'customer.expansion_candidate' },
    onExit: { emit: 'customer.expansion_candidate.resolved' },
  },
]);

Keep the feature traits synchronized from your entitlement and usage systems:

await kite.identify({
  customerId: 'account_42',
  traits: {
    plan: 'pro',
    availableFeatures: ['exports', 'sso'],
    usedFeatures: ['exports'],
  },
});

Do not use featuresUnused in a rule condition; it requires the segment engine. If a rule uses emit_signal, its signal must reference an existing defineSignals ID.

Test the outcome

Deploy to test and exercise examples that should and should not qualify:

kite validate --strict
kite deploy --env test
kite events send account.created --customer account_42 --env test
kite events send integration.connected --customer account_42 \
  --props '{"provider":"salesforce"}' --env test
kite events send report.exported --customer account_42 \
  --props '{"format":"csv"}' --env test
kite customers inspect account_42 --env test
kite journeys inspect account_42 activation --env test
kite segments inspect at_risk --env test
kite rules logs --customer account_42 --detail --env test

Backtest against known customers, record false positives, and choose windows from observed product cadence. Before changing semantics for existing customers, run:

kite recompute --all --dry-run --env test

The dry-run includes aggregate impact plus complete per-customer differences for lifecycle, health, segments, and journeys, along with predicted rule executions and corrective emissions. Review Backfill and recomputation before persisting a large run.

On this page