Stripe Integration Guide
Get secure webhook validation working in your .NET API in under 5 minutes.
Complete Data Isolation
Primus Payments SDK runs entirely within your application. No payment data is transmitted to Primus servers. All webhook validation happens locally using your provider's signing secrets.
Providers
The Payments module currently supports Stripe. More providers coming soon.
Provider 1: Stripe
Step 1: Install Package
dotnet add package PrimusSaaS.Payments
Step 2: Configure in Program.cs
using Primus.Payments;
using Primus.Payments.Abstractions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPrimusPayments(payments =>
{
payments.UseStripe(opts =>
{
opts.WebhookSecret = builder.Configuration["Stripe:WebhookSecret"];
opts.ToleranceSeconds = 300; // 5 minutes tolerance
});
payments.OnPaymentSucceeded<PaymentSucceededHandler>();
payments.OnSubscriptionCreated<SubscriptionCreatedHandler>();
});
var app = builder.Build();
app.MapPrimusPaymentsWebhook("/webhooks/stripe");
app.Run();
Step 3: Configure appsettings.json
Add configuration to your appsettings.json:
{
"Stripe": {
"WebhookSecret": "whsec_..."
}
}
How to Get Stripe Configuration Values
Stripe Dashboard:
- Log in to dashboard.stripe.com
- Go to Developers > Webhooks
- Click Add endpoint (or select existing)
- Use
http://localhost:5000/webhooks/stripefor local testing - Once created, click Reveal under "Signing secret"
- Copy the value starting with
whsec_
Important: Ensure you use the correct secret. Live and Test modes have different secrets.
Step 4: Create Event Handler
Create Services/PaymentHandlers.cs:
using Primus.Payments.Abstractions;
public sealed class PaymentSucceededHandler
: IPaymentEventHandler<PaymentSucceededEvent>
{
private readonly ILogger<PaymentSucceededHandler> _logger;
public PaymentSucceededHandler(ILogger<PaymentSucceededHandler> logger)
{
_logger = logger;
}
public Task HandleAsync(
PaymentSucceededEvent evt,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Payment received: {Amount} {Currency}",
evt.Amount,
evt.Currency);
return Task.CompletedTask;
}
}
public sealed class SubscriptionCreatedHandler
: ISubscriptionEventHandler<SubscriptionCreatedEvent>
{
private readonly ILogger<SubscriptionCreatedHandler> _logger;
public SubscriptionCreatedHandler(ILogger<SubscriptionCreatedHandler> logger)
{
_logger = logger;
}
public Task HandleAsync(
SubscriptionCreatedEvent evt,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Subscription created: {SubscriptionId}",
evt.SubscriptionId);
return Task.CompletedTask;
}
}
Step 5: Test Endpoints
Using Stripe CLI:
# 1. Forward webhooks to local server
stripe listen --forward-to localhost:5000/webhooks/stripe
# 2. Trigger a test event in another terminal
stripe trigger payment_intent.succeeded
Expected Log Output:
info: PaymentSucceededHandler[0]
Payment received: 2000 usd
Configuration Reference
Stripe Options
| Option | Type | Default | Description |
|---|---|---|---|
WebhookSecret | string | - | Stripe webhook signing secret (whsec_...) |
ApiKey | string? | - | Optional API key for Stripe API calls |
ToleranceSeconds | int | 300 | Allowed timestamp drift window in seconds |
SignatureHeaderName | string | Stripe-Signature | Header containing the signature |
Examples
Example 1: Handling Subscriptions
public sealed class SubscriptionCreatedHandler
: ISubscriptionEventHandler<SubscriptionCreatedEvent>
{
public async Task HandleAsync(
SubscriptionCreatedEvent evt,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"New subscription: {Id}",
evt.SubscriptionId);
await _userService.GrantAccessAsync(evt.CustomerId);
}
}
Troubleshooting
Issue: Invalid Signature
Error: Invalid signature or BadRequest
Solution:
- Verify the
WebhookSecretmatches the one in your Stripe Dashboard. - Ensure you are sending the raw body string to the validator, not a serialized object.
- Check if the
Stripe-Signatureheader is present.
Issue: Timestamp too old
Error: Timestamp outside the tolerance window
Solution:
- Increase
ToleranceSecondsin configuration if testing with old payloads. - Ensure your server clock is synchronized.