Overview
Subscription tracking helps you monitor your SaaS revenue metrics, understand user subscription behavior, and predict churn. The SDK provides automatic payment provider detection (Stripe, Paddle, Chargebee) and comprehensive subscription lifecycle tracking.
Quick Start
Identify Users with Subscription Data
When users sign up or subscribe, identify them with subscription properties:import { useAnalytics } from "mentiq-sdk";
function SubscriptionSuccess({ subscription }) {
const { identify } = useAnalytics();
useEffect(() => {
identify(user.id, {
email: user.email,
subscription: {
status: "active",
plan_name: "Pro",
plan_tier: "pro",
mrr: 9900, // Monthly recurring revenue in cents
billing_interval: "month",
provider: "stripe",
},
});
}, []);
return <SuccessMessage />;
}
Track Subscription Events
Track lifecycle events as they happen:const { trackSubscriptionStarted } = useAnalytics();
const handleSubscribe = async (plan) => {
const subscription = await createSubscription(plan);
trackSubscriptionStarted({
status: "active",
plan_name: plan.name,
mrr: plan.price,
provider: "stripe",
});
};
Monitor Churn Risk
Use built-in churn prediction to identify at-risk users:const { calculateChurnRisk } = useAnalytics();
const churnMetrics = calculateChurnRisk();
if (churnMetrics.risk_category === "high") {
// Show retention offer
}
Subscription Properties
interface SubscriptionProperties {
// Status & Plan
status: "active" | "trialing" | "past_due" | "canceled" | "paused" | "incomplete" | "incomplete_expired" | "unpaid";
plan_id?: string;
plan_name?: string;
plan_tier?: string; // e.g., "free", "starter", "pro", "enterprise"
// Pricing (in cents)
mrr?: number; // Monthly Recurring Revenue
arr?: number; // Annual Recurring Revenue
ltv?: number; // Lifetime Value
currency?: string; // e.g., "usd", "eur"
// Billing Cycle
billing_interval?: "day" | "week" | "month" | "year";
current_period_start?: string; // ISO date
current_period_end?: string; // ISO date
// Trial
trial_start?: string; // ISO date
trial_end?: string; // ISO date
is_trial?: boolean;
// Payment (PCI-safe - last 4 digits only)
payment_method_type?: string; // e.g., "card", "paypal"
payment_method_last4?: string; // Last 4 digits only
payment_method_brand?: string; // e.g., "visa", "mastercard"
// Cancellation
cancel_at_period_end?: boolean;
canceled_at?: string; // ISO date
cancellation_reason?: string;
// Provider Info
provider?: "stripe" | "paddle" | "chargebee" | "manual" | string;
provider_customer_id?: string;
provider_subscription_id?: string;
}
PCI Compliance: Never store full credit card numbers. Only use payment_method_last4 (last 4 digits). The SDK automatically validates and truncates card data.
Automatic Provider Detection
The SDK can auto-detect subscription data from Stripe, Paddle, and Chargebee by scanning browser globals and localStorage.
How It Works
From src/subscription-detection.ts:412-431:
export function autoDetectSubscription(): ProviderDetectionResult {
const results = [
detectStripeSubscription(),
detectPaddleSubscription(),
detectChargebeeSubscription(),
];
// Return highest confidence detection
const detected = results
.filter((r) => r.detected)
.sort((a, b) => b.confidence - a.confidence);
if (detected.length > 0) {
return detected[0]; // Highest confidence
}
return { detected: false, confidence: 0 };
}
Detection Confidence
Each provider detection returns a confidence score (0-100):
- 30+: Provider SDK loaded (e.g.,
window.Stripe)
- 50+: Found provider-specific localStorage keys
- 70+: Extracted subscription data from localStorage
- 100: Full subscription details including pricing
Stripe Detection
From src/subscription-detection.ts:29-166, the SDK checks:
window.Stripe object present (+30 confidence)
- LocalStorage keys containing “stripe”, “customer_id”, “subscription_id” (+20)
- Subscription object with plan data (+30)
- Valid customer ID starting with
cus_ (+10)
export function detectStripeSubscription(): ProviderDetectionResult {
let confidence = 0;
const subscription: Partial<SubscriptionProperties> = {
provider: "stripe",
};
// Check if Stripe.js is loaded
if (window.Stripe) {
confidence += 30;
}
// Check localStorage for Stripe data
const storageKeys = Object.keys(localStorage);
const stripeKeys = storageKeys.filter(
(key) => key.includes("stripe") || key.includes("customer_id") || key.includes("subscription_id")
);
if (stripeKeys.length > 0) {
confidence += 20;
// Extract subscription data...
}
if (confidence > 30) {
return {
detected: true,
provider: "stripe",
confidence: Math.min(confidence, 100),
subscription: subscription as SubscriptionProperties,
};
}
return { detected: false, confidence };
}
When Detection Runs
From src/analytics.ts:1377-1434, auto-detection runs:
- 1 second after SDK initialization (allows provider SDKs to load)
- Only in browser environments (
window exists)
- Results merged with manually-set subscription data (manual takes precedence)
private setupSubscriptionAutoDetection(): void {
setTimeout(() => {
const detection = autoDetectSubscription();
if (detection.detected && detection.subscription) {
console.log(
`Auto-detected ${detection.provider} subscription (confidence: ${detection.confidence}%)`
);
// Merge with existing manual data (manual takes precedence)
const existing = localStorage.getItem("mentiq_user_subscription");
const mergedSubscription = existing
? { ...detection.subscription, ...JSON.parse(existing) }
: detection.subscription;
localStorage.setItem("mentiq_user_subscription", JSON.stringify(mergedSubscription));
}
}, 1000);
}
Subscription Lifecycle Events
Track Subscription Started
import { useAnalytics } from "mentiq-sdk";
function CheckoutSuccess({ subscription }) {
const { trackSubscriptionStarted } = useAnalytics();
useEffect(() => {
trackSubscriptionStarted({
status: "active",
plan_name: "Pro Plan",
plan_tier: "pro",
mrr: 9900, // $99.00 in cents
currency: "usd",
billing_interval: "month",
provider: "stripe",
provider_subscription_id: subscription.id,
});
}, []);
return <ThankYouPage />;
}
Track Trial Started
const { trackTrialStarted } = useAnalytics();
trackTrialStarted({
status: "trialing",
plan_name: "Pro Plan",
is_trial: true,
trial_start: new Date().toISOString(),
trial_end: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(), // 14 days
provider: "stripe",
});
Track Upgrades
const { trackSubscriptionUpgraded } = useAnalytics();
trackSubscriptionUpgraded({
status: "active",
plan_name: "Enterprise",
plan_tier: "enterprise",
previous_plan: "Pro",
mrr: 29900, // $299.00
provider: "stripe",
});
Track Downgrades
const { trackSubscriptionDowngraded } = useAnalytics();
trackSubscriptionDowngraded({
status: "active",
plan_name: "Starter",
plan_tier: "starter",
previous_plan: "Pro",
mrr: 2900, // $29.00
provider: "stripe",
});
Track Cancellations
const { trackSubscriptionCanceled } = useAnalytics();
trackSubscriptionCanceled({
status: "canceled",
plan_name: "Pro",
cancellation_reason: "too_expensive",
canceled_at: new Date().toISOString(),
cancel_at_period_end: true,
provider: "stripe",
});
Track Payment Events
const { trackPaymentFailed, trackPaymentSucceeded } = useAnalytics();
// Payment succeeded
trackPaymentSucceeded({
amount: 9900,
currency: "usd",
payment_status: "succeeded",
invoice_id: "inv_123",
});
// Payment failed
trackPaymentFailed({
amount: 9900,
currency: "usd",
payment_status: "failed",
failure_reason: "card_declined",
invoice_id: "inv_124",
});
Churn Risk Prediction
The SDK calculates churn risk based on user behavior:
import { useAnalytics } from "mentiq-sdk";
function ChurnRiskBanner() {
const { calculateChurnRisk } = useAnalytics();
const risk = calculateChurnRisk();
if (risk.risk_category === "low") return null;
return (
<div className={`alert alert-${risk.risk_category}`}>
<p>
Churn Risk: {risk.risk_category.toUpperCase()} ({risk.risk_score}/100)
</p>
{risk.intervention_recommended && (
<button>Get Help</button>
)}
</div>
);
}
Churn Risk Factors
From src/analytics.ts:630-720, risk is calculated using:
interface ChurnRiskMetrics {
risk_score: number; // 0-100
risk_category: "low" | "medium" | "high" | "critical";
factors: {
engagement_score?: number; // Low engagement = higher risk
days_since_last_active?: number; // Inactivity = higher risk
feature_adoption_rate?: number; // Low adoption = higher risk
support_tickets?: number; // Many tickets = issues
negative_feedback_count?: number; // Feedback = dissatisfaction
payment_failures?: number; // Failed payments = critical
};
predicted_churn_date?: string; // ISO date
intervention_recommended?: boolean; // Should you reach out?
}
Risk Calculation
- Engagement Score < 20: +30 risk points
- Inactive > 30 days: +40 risk points
- Feature Adoption < 20%: +15 risk points
- Support Tickets > 5: +10 risk points
- Negative Feedback > 3: +20 risk points
- Payment Failures > 2: +30 risk points
Risk Categories:
- Low: 0-24 (healthy user)
- Medium: 25-49 (monitor)
- High: 50-74 (intervention needed)
- Critical: 75-100 (immediate action)
Revenue Metrics
Track MRR/ARR
The SDK automatically calculates ARR from MRR and vice versa:
identify(user.id, {
subscription: {
mrr: 9900, // $99/month
billing_interval: "month",
// ARR automatically calculated: 9900 * 12 = 118,800
},
});
Get Current Subscription
const { getSubscriptionData } = useAnalytics();
const subscription = getSubscriptionData();
if (subscription?.status === "active") {
console.log(`MRR: $${subscription.mrr / 100}`);
console.log(`ARR: $${subscription.arr / 100}`);
}
Auto-Enrichment
All events are automatically enriched with subscription context:
From src/analytics.ts:1141-1166:
private enqueueEvent(event: AnalyticsEvent): void {
// Auto-enrich with subscription data from localStorage
const subscriptionData = localStorage.getItem("mentiq_user_subscription");
if (subscriptionData) {
const subscription = JSON.parse(subscriptionData);
event.properties = {
...event.properties,
subscription_status: subscription.status,
subscription_plan: subscription.plan_name || subscription.plan_tier,
subscription_mrr: subscription.mrr,
subscription_provider: subscription.provider,
is_paid_user: subscription.status === "active" || subscription.status === "trialing",
};
}
this.eventQueue.push(event);
}
This means every event automatically includes:
subscription_status
subscription_plan
subscription_mrr
subscription_provider
is_paid_user
Use Cases
Stripe Integration
import { useAnalytics } from "mentiq-sdk";
import { useStripe } from "@stripe/react-stripe-js";
function StripeCheckout() {
const stripe = useStripe();
const { trackSubscriptionStarted, identify } = useAnalytics();
const handleSubscribe = async (priceId: string) => {
const { subscription } = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
});
// Track subscription
trackSubscriptionStarted({
status: subscription.status,
plan_id: subscription.items.data[0].price.id,
plan_name: subscription.items.data[0].price.nickname,
mrr: subscription.items.data[0].price.unit_amount,
billing_interval: subscription.items.data[0].price.recurring.interval,
provider: "stripe",
provider_subscription_id: subscription.id,
provider_customer_id: customerId,
});
// Also update user identity
identify(userId, {
email: user.email,
subscription: { /* same data */ },
});
};
return <CheckoutForm onSubmit={handleSubscribe} />;
}
Retention Campaigns
function RetentionBanner() {
const { calculateChurnRisk } = useAnalytics();
const risk = calculateChurnRisk();
if (risk.risk_category !== "high" && risk.risk_category !== "critical") {
return null;
}
return (
<div className="retention-banner">
<h3>We noticed you haven't been as active lately</h3>
<p>Can we help you get more value from {productName}?</p>
<button>Schedule a call</button>
<button>Get 50% off next month</button>
</div>
);
}
Best Practices
Subscription Tracking Tips:
- Always use cents for monetary amounts (9900 = $99.00)
- Track all lifecycle events (start, upgrade, downgrade, cancel)
- Include cancellation reasons for analysis
- Monitor churn risk and intervene early
- Segment users by plan tier for targeted campaigns
- Never log full credit card numbers (PCI compliance)
- Use auto-detection for faster integration
Troubleshooting
Auto-Detection Not Working
- Check payment provider SDK is loaded (e.g.,
window.Stripe)
- Verify subscription data is in localStorage
- Enable debug mode:
config.debug = true
- Wait 1 second after page load for detection to run
Subscription Data Not Persisting
Subscription data is stored in localStorage. If not persisting:
- Check browser allows localStorage
- Verify you’re calling
identify() with subscription data
- Check for localStorage quota errors
Revenue Metrics Incorrect
Ensure you’re using cents, not dollars:
// ❌ Wrong
mrr: 99 // $0.99
// ✅ Correct
mrr: 9900 // $99.00