Stripe has become the default payment platform for modern SaaS products, marketplaces, and subscription-based applications. Its API is powerful, well-documented, and flexible enough to handle everything from one-off payments to complex billing models.
But when you’re working in ASP.NET Core, especially on production systems, the question isn’t “How do I charge a card?” — it’s:
How do I correctly integrate Stripe into my backend and manage customers in a clean, scalable way?
In this post, we’ll walk through how to integrate the Stripe API with ASP.NET Core and focus specifically on creating Stripe customers, which is the foundation for subscriptions, saved payment methods, invoices, and recurring billing.
This guide assumes:
- You’re comfortable with ASP.NET Core and C#
- You’re building an API (not MVC views)
- You want a production-grade Stripe integration
Why Stripe Customers Matter
Before writing code, it’s important to understand why Stripe customers exist.
A Stripe Customer represents a real user in your system and allows you to:
- Attach payment methods (cards, bank accounts)
- Create subscriptions
- Generate invoices
- Track billing history
- Handle retries, refunds, and disputes
In most real-world systems:
- Your database user ≠ Stripe customer
- You store a
StripeCustomerIdand link the two
This separation gives you flexibility and keeps billing concerns isolated from core user logic.
Step 1: Create a Stripe Account and Get API Keys
If you haven’t already:
- Create an account at Stripe
- Switch to Test Mode
- Copy your Secret Key
You’ll use this key server-side only. Never expose it to the frontend.
Step 2: Install Stripe.NET in ASP.NET Core
Stripe provides an official .NET SDK.
Install it via NuGet:
dotnet add package Stripe.net
This package wraps Stripe’s REST API and provides strongly-typed models for customers, payments, subscriptions, and more.
Step 3: Configure Stripe in ASP.NET Core
Add Stripe settings to appsettings.json
{
"Stripe": {
"SecretKey": "sk_test_XXXXXXXXXXXXXXXXXXXXXXXX"
}
}
Create a configuration model
public class StripeSettings
{
public string SecretKey { get; set; }
}
Register Stripe in Program.cs
builder.Services.Configure<StripeSettings>(
builder.Configuration.GetSection("Stripe"));
Stripe.StripeConfiguration.ApiKey =
builder.Configuration["Stripe:SecretKey"];
This ensures Stripe is configured once at application startup.
Step 4: Decide When to Create a Stripe Customer
There are two common approaches:
Option 1: Create a Stripe customer at user registration
✔ Simple
❌ Creates unused customers
Option 2: Create a Stripe customer only when billing starts
✔ Cleaner
✔ More efficient
✔ Preferred for SaaS apps
We’ll follow Option 2, which means creating a Stripe customer on demand.
Step 5: Design a Customer Creation Endpoint
Your backend should own Stripe interactions. The frontend never talks to Stripe directly when creating customers.
Example API endpoint
POST /api/billing/customers
This endpoint:
- Accepts user information
- Creates a Stripe customer
- Saves the Stripe customer ID in your database
Step 6: Create a Stripe Customer Service
Encapsulating Stripe logic in a service keeps your controllers clean and testable.
Create IStripeCustomerService
public interface IStripeCustomerService
{
Task<string> CreateCustomerAsync(
string email,
string fullName,
Guid userId);
}
Implement the service
using Stripe;
public class StripeCustomerService : IStripeCustomerService
{
private readonly CustomerService _customerService;
public StripeCustomerService()
{
_customerService = new CustomerService();
}
public async Task<string> CreateCustomerAsync(
string email,
string fullName,
Guid userId)
{
var options = new CustomerCreateOptions
{
Email = email,
Name = fullName,
Metadata = new Dictionary<string, string>
{
{ "UserId", userId.ToString() }
}
};
var customer = await _customerService.CreateAsync(options);
return customer.Id;
}
}
Why metadata matters
Stripe metadata allows you to:
- Correlate Stripe objects with internal users
- Debug issues quickly
- Avoid complex lookup logic later
Always store your internal user ID in Stripe metadata.
Step 7: Register the Service
In Program.cs:
builder.Services.AddScoped<IStripeCustomerService, StripeCustomerService>();
Step 8: Create the API Controller
Request model
public class CreateStripeCustomerRequest
{
public string Email { get; set; }
public string FullName { get; set; }
}
Controller implementation
[ApiController]
[Route("api/billing/customers")]
public class BillingController : ControllerBase
{
private readonly IStripeCustomerService _stripeCustomerService;
public BillingController(IStripeCustomerService stripeCustomerService)
{
_stripeCustomerService = stripeCustomerService;
}
[HttpPost]
public async Task<IActionResult> CreateCustomer(
CreateStripeCustomerRequest request)
{
// Example: retrieve userId from auth context
var userId = Guid.Parse(User.FindFirst("sub").Value);
var stripeCustomerId =
await _stripeCustomerService.CreateCustomerAsync(
request.Email,
request.FullName,
userId);
// Save stripeCustomerId to your database here
return Ok(new
{
StripeCustomerId = stripeCustomerId
});
}
}
Step 9: Persist the Stripe Customer ID
In your database, your user table should include:
StripeCustomerId (nullable)
Once created, you never need to create another Stripe customer for that user.
Every future billing action uses this ID:
- Attach payment methods
- Create subscriptions
- Generate invoices
Step 10: Common Mistakes to Avoid
❌ Creating customers multiple times
Always check if StripeCustomerId already exists before creating a new one.
❌ Exposing Stripe secret keys
Stripe secret keys must never leave the backend.
❌ Mixing Stripe logic into controllers
Stripe logic belongs in services, not controllers.
❌ Skipping metadata
You’ll regret this when debugging production issues.
Handling Errors Gracefully
Stripe throws rich exceptions. Catch them explicitly:
try
{
// Stripe call
}
catch (StripeException ex)
{
// Log ex.StripeError.Message
throw;
}
Always log Stripe errors with:
- Request ID
- User ID
- Operation type
Testing in Stripe Test Mode
Stripe provides test data:
- Card:
4242 4242 4242 4242 - Any future expiration date
- Any CVC
Test mode behaves almost identically to live mode — use it extensively.
When to Create Customers vs Payment Intents
| Feature | Customer | Payment Intent |
|---|---|---|
| User identity | ✔ | ❌ |
| Saved cards | ✔ | ❌ |
| Subscriptions | ✔ | ❌ |
| One-off payments | Optional | ✔ |
Most SaaS apps use both:
- Customers for identity
- Payment Intents for charging
Final Thoughts
Integrating Stripe with ASP.NET Core isn’t hard — but doing it correctly makes a massive difference as your application grows.
By:
- Creating customers intentionally
- Isolating Stripe logic into services
- Persisting Stripe IDs properly
- Using metadata consistently
You end up with a billing system that is:
- predictable
- debuggable
- scalable
- production-ready
Creating Stripe customers is the foundation for everything else Stripe offers. Get this part right, and subscriptions, invoices, and payments become straightforward instead of painful.