Introduction
The Software as a Service (SaaS) model has revolutionized how software is delivered and monetized. Instead of one-time purchases, users subscribe to a service on a recurring basis — monthly, quarterly, or annually.
To manage these subscriptions securely and efficiently, developers often integrate trusted payment gateways like Stripe . This article explores how to build a SaaS platform with subscription management using Stripe and ASP.NET Core , covering key architectural concepts, implementation steps, webhook handling, and real-world best practices.
Understanding SaaS Subscription Architecture
In a subscription-based platform, you need to manage:
User accounts (signup, authentication, roles)
Subscription plans (free, standard, premium tiers)
Recurring billing (via Stripe)
Webhooks for payment events (e.g., renewal, cancellation)
Access control (features based on plan type)
High-Level Workflow
[User Registers]
↓
[Selects Subscription Plan]
↓
[Redirected to Stripe Checkout]
↓
[Payment Confirmation via Stripe Webhook]
↓
[Subscription Activated in ASP.NET Core DB]
↓
[Access Granted Based on Plan Level]
Tech Stack Overview
| Layer | Technology | Purpose |
|---|
| Frontend | Angular / React | Subscription UI & Checkout |
| Backend | ASP.NET Core 9 | API endpoints, logic, webhook handling |
| Database | SQL Server | Store users, subscriptions, invoices |
| Payment Gateway | Stripe | Billing, subscription, and invoicing |
| Authentication | ASP.NET Identity + JWT | Secure user sessions |
Setting up Stripe
Step 1: Create a Stripe Account
Go to https://dashboard.stripe.com → Create a test account.
Step 2: Obtain API Keys
Navigate to Developers → API Keys
Step 3: Create Subscription Plans
In the Stripe Dashboard:
Products → Add Product → “Pro Plan”, “Enterprise Plan”
Add Pricing → Monthly / Yearly
Each plan will have a Price ID (e.g., price_12345 ).
Implementing Stripe in ASP.NET Core
Step 1: Install Stripe NuGet Package
dotnet add package Stripe.net
Step 2: Configure Stripe Settings
In appsettings.json :
{"Stripe": {
"PublishableKey": "pk_test_abc123",
"SecretKey": "sk_test_xyz789",
"WebhookSecret": "whsec_XXXX"}}
In Program.cs :
builder.Services.Configure<StripeSettings>(builder.Configuration.GetSection("Stripe"));
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
Step 3: Create the Checkout Session
In SubscriptionController.cs :
using Microsoft.AspNetCore.Mvc;
using Stripe.Checkout;
[ApiController]
[Route("api/[controller]")]
public class SubscriptionController : ControllerBase
{
[HttpPost("create-checkout-session")]
public ActionResult CreateCheckoutSession([FromBody] string priceId)
{
var domain = "https://localhost:4200";
var options = new SessionCreateOptions
{
SuccessUrl = $"{domain}/success?session_id={{CHECKOUT_SESSION_ID}}",
CancelUrl = $"{domain}/cancel",
PaymentMethodTypes = new List<string> { "card" },
Mode = "subscription",
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
Price = priceId,
Quantity = 1
}
}
};
var service = new SessionService();
var session = service.Create(options);
return Ok(new { url = session.Url });
}
}
Step 4: Angular Frontend Integration
Angular Service Example:
createCheckout(priceId: string) {
return this.http.post<any>('https://localhost:5001/api/subscription/create-checkout-session', { priceId });
}
Component Example:
this.subscriptionService.createCheckout('price_12345').subscribe((res) => {
window.location.href = res.url;
});
This redirects the user to Stripe’s hosted checkout page securely.
Handling Stripe Webhooks
Webhooks are essential for automatically updating your system when events occur — such as successful payments, cancellations, or subscription renewals.
Step 1: Create Webhook Endpoint
[HttpPost("webhook")]
public async Task<IActionResult> StripeWebhook()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ConstructEvent(
json,
Request.Headers["Stripe-Signature"],
_config["Stripe:WebhookSecret"]
);
if (stripeEvent.Type == Events.InvoicePaymentSucceeded)
{
var invoice = stripeEvent.Data.Object as Stripe.Invoice;
// Update subscription status in DB
}
else if (stripeEvent.Type == Events.CustomerSubscriptionDeleted)
{
var subscription = stripeEvent.Data.Object as Stripe.Subscription;
// Mark subscription as canceled
}
return Ok();
}
catch (StripeException e)
{
return BadRequest(e.Message);
}
}
Step 2: Register Webhook in Stripe Dashboard
Go to:
Developers → Webhooks → Add Endpoint
Set URL to your deployed API:
https://myapi.com/api/subscription/webhook
Managing Subscriptions in Database
You’ll need database tables like:
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY,
Email NVARCHAR(100),
StripeCustomerId NVARCHAR(50)
);
CREATE TABLE Subscriptions (
SubscriptionId INT PRIMARY KEY IDENTITY,
UserId INT FOREIGN KEY REFERENCES Users(UserId),
StripeSubscriptionId NVARCHAR(50),
PlanName NVARCHAR(50),
Status NVARCHAR(20),
StartDate DATETIME,
EndDate DATETIME
);
Every time a webhook event is received, update these tables accordingly.
Role-Based Access Control by Subscription Tier
In your ASP.NET Core middleware , you can conditionally grant access:
[Authorize]
[HttpGet("pro-feature")]
public IActionResult GetProFeature()
{
var plan = GetUserPlan(User.Identity.Name);
if (plan != "Pro")
return Forbid();
return Ok("Welcome to Pro Features!");
}
This ensures only paid users can access premium APIs or UI components.
CI/CD Integration and Environment Configuration
Never store your Stripe keys directly in source code.
Use Azure Key Vault , AWS Secrets Manager , or CI/CD environment variables:
dotnet user-secrets set "Stripe:SecretKey" "sk_live_xxx"
In GitHub Actions / Jenkins , inject secrets securely for staging and production environments.
Handling Cancellations and Renewals
Use Stripe’s Billing Portal for allowing users to manage subscriptions:
[HttpGet("billing-portal")]
public IActionResult CreateBillingPortal(string customerId)
{
var options = new Stripe.BillingPortal.SessionCreateOptions
{
Customer = customerId,
ReturnUrl = "https://localhost:4200/dashboard"
};
var service = new Stripe.BillingPortal.SessionService();
var session = service.Create(options);
return Ok(new { url = session.Url });
}
Performance and Scalability Considerations
Use async operations for API calls and webhook handling.
Implement retry logic for webhook events.
Cache plan metadata (using MemoryCache or Redis ) to reduce Stripe API calls.
Use background jobs (Hangfire, Azure Functions) for invoice or renewal sync.
Secure all API routes with HTTPS and token-based authentication.
Example Technical Workflow
[User Signup]
↓
[Chooses Plan]
↓
[Stripe Checkout Session Created via ASP.NET Core API]
↓
[Payment Processed by Stripe]
↓
[Webhook Receives Confirmation]
↓
[Database Updates Subscription Status]
↓
[Angular Frontend Reflects Active Plan]
Best Practices
Test using Stripe Test Mode before going live.
Validate all incoming webhook events.
Log all subscription changes for auditing.
Use idempotent webhook handling to prevent duplicate DB updates.
Offer grace periods for expired subscriptions.
Keep your pricing configuration in Stripe, not hardcoded.
Conclusion
Building a SaaS platform with Stripe and ASP.NET Core provides a robust, secure, and scalable foundation for recurring billing and user subscription management.
By combining ASP.NET Core’s powerful backend capabilities with Stripe’s reliable billing APIs, developers can create end-to-end subscription systems — from checkout to renewal, cancellation, and access control — with minimal manual effort.
With thoughtful architecture, proper webhook handling, and secure storage of sensitive data, your SaaS platform will be ready to scale confidently for enterprise users.