SaaS Churn Analysis

v1.0.0

SaaS churn and retention analysis: cohort-based churn rates, retention curves, revenue churn vs logo churn, at-risk customer identification, expansion vs con...

0· 168·1 current·1 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for samledger67-dotcom/saas-churn-analysis.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "SaaS Churn Analysis" (samledger67-dotcom/saas-churn-analysis) from ClawHub.
Skill page: https://clawhub.ai/samledger67-dotcom/saas-churn-analysis
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Bare skill slug

openclaw skills install saas-churn-analysis

ClawHub CLI

Package manager switcher

npx clawhub@latest install saas-churn-analysis
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
The name/description (cohort analysis, NRR/GRR, at-risk accounts, recovery playbooks) match the SKILL.md content: definitions, formulas, and example Python/pandas code for cohort and revenue analysis. No unrelated services, env vars, or binaries are requested.
Instruction Scope
Instructions are focused on computing retention metrics from subscription/MRR datasets and producing playbooks; they explicitly say not to perform outreach or billing actions. The examples operate on in-memory DataFrames (user-supplied data). The skill does not instruct reading unrelated system files, exfiltrating data, or calling external endpoints.
Install Mechanism
This is instruction-only (no install spec) which is low-risk. Minor inconsistency: the provided code examples use Python and pandas, but no dependencies or runtime expectations are declared. Users should ensure a Python environment with pandas is available if they expect runnable examples.
Credentials
The skill declares no required environment variables, credentials, or config paths. It expects customer/subscription data as input (appropriate for the purpose). There are no requests for unrelated secrets or external service tokens.
Persistence & Privilege
The skill is not always-enabled and does not request elevated persistence or modification of other skills or system settings. Autonomous invocation is allowed (platform default) but not combined with other concerning privileges here.
Assessment
This skill appears coherent for doing churn and retention analysis. Before installing or using it: (1) be prepared to supply your subscription and MRR datasets (containing customer/usage data) — avoid sending unnecessary PII to third parties; (2) ensure your agent/runtime has Python and pandas if you want to run the provided examples (the skill did not declare dependencies); (3) do not expect the skill to send emails or change billing — it explicitly says not to perform outreach or billing actions; and (4) review any outputs for sensitive customer data before sharing externally.

Like a lobster shell, security has layers — review code before you run it.

latestvk97caapshxgr6hg912zjd40e2983344f
168downloads
0stars
1versions
Updated 1mo ago
v1.0.0
MIT-0

SaaS Churn Analysis Skill

Deep-dive churn and retention analysis for SaaS businesses. Build cohort tables, calculate NRR/GRR, identify at-risk accounts, and produce investor-ready retention metrics with actionable recovery playbooks.


When to Use This Skill

Trigger phrases:

  • "Why are customers churning?"
  • "What's our retention rate?"
  • "Build a cohort analysis"
  • "Show me net revenue retention"
  • "Which accounts are at risk of canceling?"
  • "Investor wants to see our logo churn"
  • "What's our gross/net dollar retention?"
  • "Analyze our expansion vs contraction MRR"

NOT for:

  • Executing recovery outreach (emails, calls) — use CRM/email tools
  • Billing changes, refunds, or cancellation processing — use billing platform
  • General MRR tracking — use saas-metrics-dashboard or subscription-revenue-tracker
  • Revenue forecasting — use startup-financial-model
  • Customer success management — use a CS platform skill

Core Churn Definitions

Logo Churn (Customer Churn)

Logo Churn Rate (monthly) = Customers Lost / Customers at Start of Period

Example:
  Start of month: 200 customers
  Canceled: 5
  Logo churn rate: 5/200 = 2.5%

Revenue Churn

Gross Revenue Churn Rate = MRR Lost to Cancellations / MRR at Start of Period

Example:
  Start MRR: $100,000
  Churned MRR: $4,000 (from cancellations)
  Gross churn: 4%

Net Revenue Retention (NRR / NDR)

NRR = (Beginning MRR + Expansion MRR - Contraction MRR - Churned MRR) / Beginning MRR × 100

Components:
  + Expansion MRR: upsells, upgrades, seat additions from existing customers
  - Contraction MRR: downgrades, reduced seats
  - Churned MRR: cancellations

Example:
  Beginning MRR: $100,000
  Expansion: +$8,000
  Contraction: -$2,000
  Churn: -$4,000
  NRR = ($100,000 + $8,000 - $2,000 - $4,000) / $100,000 = 102%

NRR Benchmarks (SaaS industry):

NRRSignal
>120%Elite (enterprise, product-led)
110–120%Strong — expansion > churn
100–110%Healthy
90–100%Adequate — watch churn trends
<90%Red flag — structural problem

Gross Revenue Retention (GRR)

GRR = (Beginning MRR - Contraction MRR - Churned MRR) / Beginning MRR × 100
     (excludes expansion — pure retention, no upsell credit)

Healthy GRR benchmarks:
  Enterprise SaaS: >90%
  Mid-market: >85%
  SMB SaaS: >75%

Cohort Analysis

Building a Cohort Retention Table

Track customers by their acquisition month and measure % remaining in each subsequent month:

import pandas as pd
from datetime import datetime

def build_cohort_table(subscriptions_df: pd.DataFrame) -> pd.DataFrame:
    """
    Build a cohort retention table from subscription data.
    
    Input DataFrame columns:
        - customer_id: str
        - signup_date: datetime
        - cancel_date: datetime | None (None = still active)
    
    Returns:
        Pivot table: rows = cohort month, columns = months_since_signup,
        values = retention percentage
    """
    df = subscriptions_df.copy()
    df['cohort_month'] = df['signup_date'].dt.to_period('M')
    df['active_through'] = df['cancel_date'].fillna(pd.Timestamp.now())
    
    rows = []
    for cohort, group in df.groupby('cohort_month'):
        cohort_size = len(group)
        for month_offset in range(0, 25):  # 0–24 months
            cutoff = cohort.to_timestamp() + pd.DateOffset(months=month_offset)
            active = group[group['active_through'] >= cutoff].shape[0]
            retention = active / cohort_size * 100
            rows.append({
                'cohort': str(cohort),
                'month': month_offset,
                'cohort_size': cohort_size,
                'active': active,
                'retention_pct': round(retention, 1)
            })
    
    result = pd.DataFrame(rows)
    pivot = result.pivot(index='cohort', columns='month', values='retention_pct')
    return pivot

Example cohort table output:

Cohort     | M0    | M1    | M3    | M6    | M12
-----------|-------|-------|-------|-------|------
2025-01    | 100%  | 91%   | 81%   | 72%   | 58%
2025-02    | 100%  | 93%   | 84%   | 76%   | —
2025-03    | 100%  | 89%   | 79%   | —     | —
2025-04    | 100%  | 94%   | —     | —     | —

Revenue Cohort (Dollar Retention)

Track MRR retained and expanded per cohort:

def revenue_cohort_table(mrr_events_df: pd.DataFrame) -> pd.DataFrame:
    """
    Revenue cohort analysis tracking MRR per acquisition cohort.
    
    Input DataFrame columns:
        - customer_id: str
        - event_date: datetime
        - event_type: str  # 'signup', 'expansion', 'contraction', 'churn'
        - mrr_change: float
    
    Returns:
        Cohort revenue retention table (% of original MRR retained+expanded)
    """
    # Group by signup cohort
    signups = mrr_events_df[mrr_events_df['event_type'] == 'signup'].copy()
    signups['cohort_month'] = signups['event_date'].dt.to_period('M')
    
    # For each cohort, track MRR over time
    # NRR by cohort = sum(all MRR changes for cohort customers) / initial MRR
    pass

Churn Curve Analysis

Identify when in the customer lifecycle churn peaks:

Early churn (M1-M3): Onboarding failure, value not delivered
  → Diagnosis: activation rate, time-to-first-value, support tickets
  
Mid-term churn (M4-M12): Competitive displacement, budget cuts
  → Diagnosis: NPS trends, feature adoption, renewal engagement
  
Late churn (M12+): Strategic shifts, contract terms, enterprise competition
  → Diagnosis: executive sponsor changes, usage trends, renewal conversations

Churn by tenure bucket:

def churn_by_tenure(subscriptions_df: pd.DataFrame) -> dict:
    """Calculate churn rate for different tenure buckets."""
    buckets = {
        '0-3mo': (0, 90),
        '3-6mo': (90, 180),
        '6-12mo': (180, 365),
        '12-24mo': (365, 730),
        '24mo+': (730, float('inf'))
    }
    
    results = {}
    for bucket_name, (min_days, max_days) in buckets.items():
        mask = (
            (subscriptions_df['tenure_days'] >= min_days) &
            (subscriptions_df['tenure_days'] < max_days)
        )
        bucket_df = subscriptions_df[mask]
        if len(bucket_df) == 0:
            continue
        churned = bucket_df[bucket_df['cancel_date'].notna()].shape[0]
        results[bucket_name] = {
            'total_customers': len(bucket_df),
            'churned': churned,
            'churn_rate_pct': round(churned / len(bucket_df) * 100, 1)
        }
    return results

At-Risk Customer Identification

Churn Risk Scoring

Score each active customer by leading indicators:

CHURN_RISK_WEIGHTS = {
    'days_since_last_login': 0.25,       # Usage drop
    'feature_adoption_pct': -0.20,       # Inverse: more features = lower risk
    'support_tickets_30d': 0.15,         # Escalations
    'nps_score': -0.15,                  # Inverse: high NPS = lower risk
    'days_to_renewal': -0.10,            # Closer renewal = higher urgency
    'billing_failures_90d': 0.15,        # Payment issues
}

def churn_risk_score(customer: dict) -> float:
    """
    Calculate 0-100 churn risk score for a customer.
    Higher = more likely to churn.
    
    Inputs:
        customer: dict with keys matching CHURN_RISK_WEIGHTS
    
    Returns:
        Risk score 0-100 (>70 = high risk, 40-70 = medium, <40 = low)
    """
    raw_score = 0
    for factor, weight in CHURN_RISK_WEIGHTS.items():
        if factor in customer:
            # Normalize each factor to 0-100 scale first
            normalized = normalize_factor(factor, customer[factor])
            raw_score += normalized * weight
    
    # Scale to 0-100
    return max(0, min(100, raw_score * 100 + 50))

def get_at_risk_accounts(customers: list, threshold: float = 70.0) -> list:
    """Return customers with churn risk score above threshold, sorted by MRR."""
    at_risk = [
        {**c, 'risk_score': churn_risk_score(c)}
        for c in customers
    ]
    return sorted(
        [c for c in at_risk if c['risk_score'] >= threshold],
        key=lambda x: x.get('mrr', 0),
        reverse=True  # Highest MRR first — prioritize by revenue impact
    )

Early Warning Signals

Usage-based signals (product telemetry):

🔴 High risk:
  - No login in 14+ days (was weekly user)
  - DAU/MAU ratio dropped >50% MoM
  - Core feature not used in 30 days
  - Below 20% feature adoption vs peers

🟡 Medium risk:
  - Login frequency dropped >30% MoM
  - Support ticket with "cancel" or "refund" keyword
  - NPS score ≤ 6 (detractor)
  - Seat count reduced

🟢 Healthy signals:
  - Expanded seats or upgraded tier
  - Used 3+ core features this month
  - NPS ≥ 9 (promoter)
  - Referred another customer

Financial signals:

🔴 High risk:
  - Payment failure (retry in progress)
  - Requested invoice-based payment shift (budget freeze)
  - Contract not opened with 30 days to renewal

🟡 Medium risk:
  - Asked about pricing alternatives
  - Billing contact changed
  - Discount request submitted

MRR Movement Analysis

MRR Bridge

Decompose monthly MRR change into components:

MRR Bridge: January → February

Beginning MRR:     $100,000
+ New Business:      +$8,500   (23 new customers × $370 avg)
+ Expansion:         +$3,200   (upgrades + seat additions)
- Contraction:       -$1,100   (downgrades + seat reductions)
- Churn:             -$4,300   (11 cancellations × $390 avg)
= Ending MRR:      $106,300

Net New MRR:        +$6,300
MoM Growth:          6.3%

Python MRR bridge calculation:

from dataclasses import dataclass

@dataclass
class MRRBridge:
    period: str
    beginning_mrr: float
    new_mrr: float           # New customers
    expansion_mrr: float     # Upsells/upgrades
    contraction_mrr: float   # Downgrades (negative or positive — store as positive)
    churned_mrr: float       # Cancellations (store as positive)
    
    @property
    def ending_mrr(self) -> float:
        return self.beginning_mrr + self.new_mrr + self.expansion_mrr - self.contraction_mrr - self.churned_mrr
    
    @property
    def net_new_mrr(self) -> float:
        return self.ending_mrr - self.beginning_mrr
    
    @property
    def growth_rate_pct(self) -> float:
        return self.net_new_mrr / self.beginning_mrr * 100 if self.beginning_mrr else 0
    
    @property
    def quick_ratio(self) -> float:
        """SaaS Quick Ratio = (New + Expansion) / (Contraction + Churn). >4 = healthy."""
        numerator = self.new_mrr + self.expansion_mrr
        denominator = self.contraction_mrr + self.churned_mrr
        return numerator / denominator if denominator else float('inf')
    
    def to_summary(self) -> str:
        return (
            f"MRR Bridge ({self.period})\n"
            f"  Beginning: ${self.beginning_mrr:,.0f}\n"
            f"  + New:       ${self.new_mrr:,.0f}\n"
            f"  + Expansion: ${self.expansion_mrr:,.0f}\n"
            f"  - Contraction: ${self.contraction_mrr:,.0f}\n"
            f"  - Churn:     ${self.churned_mrr:,.0f}\n"
            f"  = Ending:  ${self.ending_mrr:,.0f}\n"
            f"  Growth: {self.growth_rate_pct:.1f}% | Quick Ratio: {self.quick_ratio:.1f}x"
        )

SaaS Quick Ratio benchmarks:

Quick RatioSignal
>4Elite growth efficiency
2–4Healthy
1–2Growing but inefficient — churn drag
<1Shrinking — churn exceeds new + expansion

Churn Recovery Playbooks

Playbook 1: Early Churn (Month 1-3)

Root cause: Failed onboarding, didn't reach first value moment

Diagnosis questions:

□ Did they complete onboarding? (activation rate)
□ Did they use the core feature at least once? (activation event)
□ How long did it take to reach first value moment?
□ Did they get a human touchpoint in first 48 hours?

Recovery actions:

Day 1-7:   Personal outreach from CSM — "What would make this a 10/10?"
Day 7-14:  Offer 1:1 onboarding session + extend trial if applicable
Day 14-21: Share customer success story in their industry/use case
Day 21-30: Executive touchpoint if MRR > $500/mo

Playbook 2: Mid-Term Churn (Month 4-12)

Root cause: Value plateau, competitive evaluation, budget pressure

Diagnosis questions:

□ Usage trend: up, flat, or declining in last 60 days?
□ When did they last use the feature most tied to their stated goal?
□ Any support escalations or complaints in the last 90 days?
□ Have they been pitched by a competitor? (ask directly)
□ Is this a budget-driven decision or product-driven?

Recovery actions by root cause:

Budget:
  → Offer pause plan (90-day pause vs cancel)
  → Right-size to smaller plan vs lose them entirely
  → Annual prepay at 20% discount to lock in

Product gaps:
  → Roadmap call with PM — "here's what's coming"
  → Workaround documentation for their specific use case
  → Connect to power-user customer for peer validation

Competitor evaluation:
  → Direct competitive comparison matrix
  → Migration cost analysis (switching is expensive)
  → Win-back offer if they've already left (45-day re-engagement)

Playbook 3: Renewal-at-Risk (30-60 days to renewal)

Proactive renewal pipeline:

60 days out:
  □ Usage review: send personalized "Your results with [Product]" email
  □ Identify any open issues — resolve before renewal conversation

45 days out:
  □ QBR or check-in call — confirm value, surface upsell opportunity
  □ Flag to AE if NPS < 7 or usage declining

30 days out:
  □ Renewal proposal sent — include current plan + upsell option
  □ Executive sponsor confirmation (for accounts >$1k/mo)

14 days out:
  □ Follow-up if no response — switch to phone
  □ Escalate to manager if no reply

7 days out:
  □ Final decision call — accept reduced terms if needed to retain

Output Formats

Investor-Ready Retention Summary

{
  "period": "Q4 2025",
  "generated_at": "2026-01-15",
  "retention_metrics": {
    "logo_churn_rate_monthly": 2.1,
    "mrr_gross_churn_rate_monthly": 3.8,
    "net_revenue_retention_pct": 108,
    "gross_revenue_retention_pct": 96.2,
    "quick_ratio": 3.2
  },
  "mrr_bridge": {
    "beginning_mrr": 285000,
    "new_mrr": 42000,
    "expansion_mrr": 18500,
    "contraction_mrr": 4200,
    "churned_mrr": 10800,
    "ending_mrr": 330500
  },
  "at_risk_pipeline": {
    "high_risk_count": 8,
    "high_risk_mrr_at_risk": 24600,
    "medium_risk_count": 15,
    "medium_risk_mrr_at_risk": 38200
  },
  "cohort_highlights": {
    "best_cohort": { "month": "2025-03", "m12_retention": 74 },
    "worst_cohort": { "month": "2025-08", "m3_retention": 71 },
    "avg_m12_retention": 68.5
  },
  "benchmarks": {
    "nrr_vs_industry": "above_median",
    "grr_vs_industry": "top_quartile",
    "logo_churn_vs_industry": "median"
  }
}

CSV Export for Spreadsheets

cohort_retention_csv_template:
Cohort,Size,M1,M2,M3,M6,M9,M12,M18,M24
2025-01,45,91%,84%,81%,73%,67%,61%,55%,49%
2025-02,52,93%,87%,83%,—,—,—,—,—
...

Step-by-Step Workflow

Full Churn Audit

Step 1: Data collection

□ Customer list with signup date and cancel date (if churned)
□ MRR per customer per month (last 12 months)
□ Usage data: logins, feature events (from product analytics)
□ NPS scores if available
□ Cancellation reason codes (from offboarding flow)

Step 2: Calculate headline metrics

  • Logo churn rate (monthly and annualized)
  • Gross and net revenue retention
  • Quick ratio
  • Churn by tenure bucket

Step 3: Build cohort table

  • M0–M12 retention by acquisition cohort
  • Identify best and worst cohorts — find what's different

Step 4: MRR bridge (last 6 months)

  • New vs expansion vs contraction vs churn
  • Trend analysis: is churn improving or worsening?

Step 5: At-risk identification

  • Score all active customers by churn risk signals
  • Prioritize by MRR at risk (highest first)
  • Output: top 10 at-risk accounts with reasons

Step 6: Root cause analysis

  • What's driving churn? (onboarding failure, competition, budget, product gaps)
  • Which segments have highest churn? (plan size, industry, use case, acquisition channel)

Step 7: Recommend playbook

  • Match root cause to recovery playbook
  • Estimate MRR at stake if intervention succeeds (recovery potential)
  • Prioritize actions by expected ROI

Churn by Segment Analysis

Segment churn to find structural patterns:

def churn_by_segment(subscriptions_df: pd.DataFrame, segment_col: str) -> pd.DataFrame:
    """
    Calculate churn rate by customer segment.
    
    Args:
        segment_col: column name to segment by (e.g., 'plan', 'industry', 'company_size')
    
    Returns:
        DataFrame with churn rate per segment, sorted by MRR impact
    """
    results = []
    for segment, group in subscriptions_df.groupby(segment_col):
        total = len(group)
        churned = group[group['cancel_date'].notna()].shape[0]
        total_mrr = group['mrr'].sum()
        churned_mrr = group[group['cancel_date'].notna()]['mrr'].sum()
        
        results.append({
            'segment': segment,
            'total_customers': total,
            'churned_customers': churned,
            'logo_churn_pct': round(churned / total * 100, 1),
            'total_mrr': total_mrr,
            'churned_mrr': churned_mrr,
            'mrr_churn_pct': round(churned_mrr / total_mrr * 100, 1) if total_mrr else 0
        })
    
    return pd.DataFrame(results).sort_values('churned_mrr', ascending=False)

Key segments to analyze:

  • By plan tier (free trial → paid → enterprise)
  • By acquisition channel (organic, paid, referral)
  • By company size (SMB, mid-market, enterprise)
  • By industry vertical
  • By geographic region
  • By sales rep / CSM (is one rep's book churning faster?)

Integration Points

  • saas-metrics-dashboard — Display NRR, GRR, and churn rate KPIs in dashboard
  • kpi-alert-system — Trigger alerts when monthly churn exceeds threshold
  • startup-financial-model — Feed churn rate assumptions into revenue forecasts
  • subscription-revenue-tracker — MRR bridge data source for churn calculations
  • crypto-tax-agent — N/A (different domain)

Key Formulas Cheat Sheet

Logo Churn Rate (monthly)  = Customers Lost / Customers at Start × 100
Annual Logo Churn          = 1 - (1 - monthly_churn)^12 × 100
Gross Revenue Retention    = (BOM MRR - Contraction - Churn) / BOM MRR × 100
Net Revenue Retention      = (BOM MRR + Expansion - Contraction - Churn) / BOM MRR × 100
Quick Ratio                = (New MRR + Expansion MRR) / (Contraction MRR + Churned MRR)
LTV (with churn)           = ARPU / Monthly Churn Rate
Avg Customer Lifetime      = 1 / Monthly Churn Rate (in months)

Rule of Thumb:
  2% monthly logo churn  = ~21% annual churn (B2B SMB benchmark)
  0.5% monthly logo churn = ~6% annual churn (enterprise benchmark)
  NRR >100% means you grow from existing base alone — key investor signal

Comments

Loading comments...