Skip to main content

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:

  1. Log in to dashboard.stripe.com
  2. Go to Developers > Webhooks
  3. Click Add endpoint (or select existing)
  4. Use http://localhost:5000/webhooks/stripe for local testing
  5. Once created, click Reveal under "Signing secret"
  6. 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

OptionTypeDefaultDescription
WebhookSecretstring-Stripe webhook signing secret (whsec_...)
ApiKeystring?-Optional API key for Stripe API calls
ToleranceSecondsint300Allowed timestamp drift window in seconds
SignatureHeaderNamestringStripe-SignatureHeader 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:

  1. Verify the WebhookSecret matches the one in your Stripe Dashboard.
  2. Ensure you are sending the raw body string to the validator, not a serialized object.
  3. Check if the Stripe-Signature header is present.

Issue: Timestamp too old

Error: Timestamp outside the tolerance window

Solution:

  1. Increase ToleranceSeconds in configuration if testing with old payloads.
  2. Ensure your server clock is synchronized.