Introduction
Subscription-based business models generate predictable recurring revenue and are the dominant monetization strategy for SaaS products. As a developer, your technical skills give you a significant advantage in building, measuring, and optimizing a subscription business. This guide covers the essential components from pricing strategy to billing implementation and metric tracking.
Pricing Tier Design
Effective pricing tiers balance value capture with customer acquisition:
# Pricing strategy framework
tiers:
free:
monthly_price: 0
features:
- Up to 100 API calls/day
- 7-day data retention
- Community support
limitations:
- No custom domains
- Rate limit: 10 req/min
goal: "Acquisition and onboarding"
pro:
monthly_price: 29
features:
- Up to 10,000 API calls/day
- 90-day data retention
- Email support (24h response)
- Custom domains
limitations: []
goal: "Primary revenue driver"
team:
monthly_price: 99
features:
- Up to 100,000 API calls/day
- 1-year data retention
- Priority support (4h response)
- Team accounts (up to 5 seats)
- API analytics dashboard
limitations: []
goal: "Team adoption and expansion"
enterprise:
monthly_price: null # Custom pricing
features:
- Unlimited API calls
- Unlimited retention
- Dedicated support engineer
- SSO/SAML
- Custom SLA
- On-premise option
limitations: []
goal: "High-value accounts"
Pricing psychology tips:
Stripe Billing Integration
// Stripe subscription management
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2025-09-01',
});
// Create a checkout session for subscription
async function createCheckoutSession(
customerId: string,
priceId: string,
successUrl: string,
cancelUrl: string
) {
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
line_items: [{
price: priceId,
quantity: 1,
}],
subscription_data: {
metadata: {
source: 'direct_checkout',
plan_tier: priceId === PRICE_PRO_MONTHLY ? 'pro' : 'team',
},
trial_period_days: 14,
},
success_url: successUrl,
cancel_url: cancelUrl,
});
return session.url;
}
// Handle subscription lifecycle via webhooks
async function handleSubscriptionWebhook(event: Stripe.Event) {
switch (event.type) {
case 'customer.subscription.created':
await onSubscriptionCreated(event.data.object);
break;
case 'customer.subscription.updated':
await onSubscriptionUpdated(event.data.object);
break;
case 'customer.subscription.deleted':
await onSubscriptionCancelled(event.data.object);
break;
case 'invoice.payment_failed':
await onPaymentFailed(event.data.object);
break;
case 'invoice.payment_succeeded':
await onPaymentSucceeded(event.data.object);
break;
}
}
async function onPaymentFailed(invoice: Stripe.Invoice) {
// Send dunning emails
if (invoice.attempt_count === 1) {
await sendEmail({
to: invoice.customer_email,
subject: "Payment failed - please update your billing info",
template: "payment_failed_first_attempt",
});
}
// Notify via Slack for manual follow-up
if (invoice.attempt_count >= 3) {
await notifySlack(
`Payment failed 3 times for ${invoice.customer_email}. ` +
`Subscription may be downgraded soon.`
);
}
}
Churn Reduction Strategies
Track churn with event analytics and implement proactive retention:
// Churn prediction and intervention
interface ChurnRiskFactors {
daysSinceLastLogin: number;
apiCallDeclinePercent: number;
supportTicketsOpen: number;
paymentFailures: number;
}
function calculateChurnRisk(factors: ChurnRiskFactors): 'low' | 'medium' | 'high' {
let score = 0;
// Login frequency
if (factors.daysSinceLastLogin > 14) score += 2;
if (factors.daysSinceLastLogin > 30) score += 3;
// Usage decline
if (factors.apiCallDeclinePercent > 50) score += 2;
// Support issues
if (factors.supportTicketsOpen > 2) score += 2;
// Billing problems
if (factors.paymentFailures > 0) score += 3;
if (score >= 5) return 'high';
if (score >= 3) return 'medium';
return 'low';
}
async function applyChurnIntervention(userId: string, risk: ChurnRiskFactors) {
// Schedule automated outreach
if (risk.daysSinceLastLogin > 7) {
await sendEmail({
userId,
template: "we_miss_you",
delay: risk.daysSinceLastLogin > 14 ? 0 : 24 * 60 * 60 * 1000,
});
}
// Offer downgrade path before cancel
if (risk.paymentFailures > 0) {
await offerDowngradeTier(userId);
}
}
Customer Lifecycle
Map the customer journey from acquisition to expansion:
Acquisition → Activation → Retention → Revenue → Referral
| | | | |
Landing First Monthly Upgrade Share with
page value check-in prompts friends
Automate each stage:
async function handleCustomerLifecycle(userId: string, daysSinceSignup: number) {
switch (true) {
case daysSinceSignup === 0:
// Send welcome email with getting started guide
await sendWelcomeSequence(userId);
break;
case daysSinceSignup === 3:
// Check if they've used key features
const usage = await getUserUsage(userId);
if (usage.apiCalls === 0) {
await sendOnboardingReminder(userId);
}
break;
case daysSinceSignup === 14:
// Trial ending soon
await sendTrialEndingEmail(userId);
break;
case daysSinceSignup === 30:
// One-month review: ask for feedback
await sendNpsSurvey(userId);
break;
case daysSinceSignup % 90 === 0:
// Quarterly check-in
if (isPowerUser(userId)) {
await suggestUpgrade(userId);
}
break;
}
}
Key SaaS Metrics
| Metric | Formula | Target | Why It Matters |
|---|---|---|---|
| MRR | Monthly recurring revenue | Growing 10-20% month-over-month | Core health metric |
| ARR | MRR x 12 | >$100K for seed stage | Annual run rate |
| LTV | ARPU / Churn Rate | >3x CAC | Customer lifetime value |
| CAC | Sales + Marketing / New Customers | <$500 for self-serve | Cost to acquire |
| NDR | Net Dollar Retention | >100% | Expansion minus contraction |
| Churn Rate | Cancelled / Total Customers | <5% monthly | Revenue retention |
Track these with a simple dashboard:
-- Monthly recurring revenue calculation
SELECT
DATE_TRUNC('month', subscription_start) AS month,
COUNT(*) AS customers,
SUM(monthly_price) AS mrr,
SUM(monthly_price) * 12 AS arr
FROM subscriptions
WHERE status = 'active'
GROUP BY 1
ORDER BY 1 DESC;
-- Churn rate
SELECT
DATE_TRUNC('month', cancelled_at) AS month,
COUNT(*)::float /
(SELECT COUNT(*) FROM subscriptions
WHERE status = 'active'
AND subscription_start < DATE_TRUNC('month', s.cancelled_at))
AS churn_rate
FROM subscriptions s
WHERE status = 'cancelled'
GROUP BY 1
ORDER BY 1 DESC;
Start with a simple pricing model (one paid tier), launch with Stripe's pre-built checkout, and focus on the first $1K MRR before optimizing pricing or building complex billing logic.