Create Stripe webhooks receiver

In this post, I create a Stripe webhooks receiver for ASP.NET Core and Blazor. This is the first post of 4 where I show the full implementation.

What is a webhook?

WebHooks is a lightweight HTTP pattern providing a simple pub/sub model for wiring together Web APIs and SaaS services. When an event happens in a service, a notification is sent in the form of an HTTP POST request to registered subscribers. The POST request contains information about the event which makes it possible for the receiver to act accordingly.

So, Stripe is one of the provider that offers a webhook for its system.

Configure Stripe

First, we have to configure a new account in the Stripe dashboard after your registration. For this example, I’m going to create a new Account Name with name PSC Test and the country is United Kingdom.

Create new account on Stripe dashboard - Create Stripe webhooks receiver
Create new account on Stripe dashboard

After that, you should be in the same environment as in the following screenshot. The new account is in Developers status and Test mode is activated.

Generate an API key in the Stripe dashboard - Create Stripe webhooks receiver
Generate an API key in the Stripe dashboard

Now, on the left side you see the Developers menu. Under API Keys, you find the keys to use in the project later. To test the webhook, you have to download the Stripe CLI from GitHub for your operating system. The CLI will help us to test the application. Download the file (it is just an exe for Windows) and save it in the project folder.

Configure your ASP.NET Core project

Now, the next step is to configure your ASP.NET Core project in order to receive the call to the webhook from Stripe. In my case, I created a solution for a Blazor application hosted in ASP.NET Core website. Therefore, I have to add the Stripe.net as a NuGet package to the solution.

Add Stripe.net as NuGet package to your projects - Create Stripe webhooks receiver
Add Stripe.net as NuGet package to your projects

There are a number of global configuration settings for Stripe.net. The one you will need to set for this tutorial is your Stripe API Key. Remember, you should never store secrets in your project’s source code. Using ASP.NET’s Secret Manager, add your API key. Also, I added my webhook endpoint signing key.

{
  "Stripe": {
    "ApiKey": "sk_test_xxxx",
    "WebhookSigningKey": "whsec_xxxx"
  }
}

Add dependency for StripeOptions

At this point, we have to save an read the configuration for Stripe. For this reason, I create a model with 2 properties: ApiKey and WebhookSigningKey.

namespace BlazorStripe.Shared.Models
{
    public class StripeOptions
    {
        public string? ApiKey { get; set; }
        public string? WebhookSigningKey { get; set; }
    }
}

Now, I have to read those values in the Program.cs and add the dependency for the project and set the ApiKey to Stripe.

builder.Services.AddTransient(_ =>
{
    return builder.Configuration.GetSection("Stripe").Get<StripeOptions>();
});

string? stripeKey = builder.Configuration["Stripe:ApiKey"];
StripeConfiguration.ApiKey = stripeKey;

Add the controller for the webhook receiver

So, your webhook listener simply be a controller with a single HttpPost endpoint. Stripe will send POST requests to this endpoint containing details related to a Stripe event. Your controller will determine which type of event it has received and take the appropriate action based on the event.

Now, create a Controllers folder in your project. Within that folder, create a class called StripeWebhook. You will make this a controller class that will be able to respond to any event received from Stripe’s webhooks.

Now, inject any services you will need in the controller using C# dependency injection. Also inject the StripeOptions interface so you will be able to access your unique webhook signing key.

private readonly string _webhookSecret;

public StripeWebhook(StripeOptions options)
{
    _webhookSecret = options?.WebhookSigningKey;
}

In the constructor above, I have extracted the WebhookSigningKey value from the Options interface and assigned it to a private variable _webhookSecret.

Next, add a single HttpPost endpoint for your controller.

This is where you will respond to events sent by Stripe. When you are designing your webhook receiver, please remember that Stripe expects your application to send an Http success response code every time an event is received.

Stripe will notify your webhook receiver for any numbers of events. For example, your application might respond when a customer updates their payment information, when a customer’s payment method will soon expire, or when a charge fails. Your controller should determine which type of event it has received and then take the appropriate action.

[HttpPost]
public async Task<IActionResult> Index()
{
    string json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();

    try
    {
        var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret);
        switch (stripeEvent.Type)
        {
            case Events.CustomerSourceUpdated:
                //make sure payment info is valid
                break;
            case Events.CustomerSourceExpiring:
                //send reminder email to update payment method
                break;
            case Events.ChargeFailed:
                //do something
                break;
        }
        return Ok();
    }
    catch (StripeException e)
    {
        return BadRequest();
    }
}

The controller action above starts by parsing the body of the request received and saving it as a string json. Then, the controller verifies that the event was sent by Stripe by comparing the value of the Stripe-Signature with our unique webhook secret.

Finally, we determine the type of event and perform the appropriate action based on the event. In this case, we might have some code that will verify that a customer’s payment method is still valid and update our database records accordingly when a customer.source.updated event is received.

Similarly, we might send a friendly reminder email in response to the customer.source.expiring event when a customer’s payment method is set to expire.

Add Swagger

Now, I like to add Swagger to the project to see the APIs and, if it is the case, test them. So, in the server project add the NuGet package Swashbuckle.AspNetCore.SwaggerUI and Swashbuckle.AspNetCore, if not automatically added.

Then, in the Program.cs add

// ...

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "ASP.NET Blazor with Stripe Webhooks",
        Version = "v1"
    });
});

var app = builder.Build();

an then this code

if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();

    app.UseSwagger();
    app.UseSwaggerUI(c => { 
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "ASP.NET Blazor with Stripe Webhooks"); 
    });
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

This configuration should be enough to see the Swagger documentation using the URL

https://localhost:7110/swagger

The result of this is the following screenshot.

Swagger for the Stripe webhooks
Swagger for the Stripe webhooks

The new Program.cs

using BlazorStripe.Shared.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.OpenApi.Models;
using Stripe;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

#region Read configuration

var configuration = builder.Configuration;
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

configuration
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{env}.json", true, true);

StripeOptions settings = new StripeOptions();
builder.Configuration.Bind(settings);

#endregion Read configuration

#region Dependecy injection

builder.Services.AddTransient(_ =>
{
    return builder.Configuration.GetSection("Stripe").Get<StripeOptions>();
});

#endregion

string? stripeKey = builder.Configuration["Stripe:ApiKey"];
StripeConfiguration.ApiKey = stripeKey;

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "ASP.NET Blazor with Stripe Webhooks",
        Version = "v1"
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();

    app.UseSwagger();
    app.UseSwaggerUI(c => { 
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "ASP.NET Blazor with Stripe Webhooks"); 
    });
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();

Test your controller

So, it is time to test your StripeWebhook controller. With the Stripe CLI that we downloaded before, we will test all our code. Now, we have to pair the Stripe CLI with our account on Stripe. Open the PowerShell (or another prompt) and execute the following line

.\stripe.exe login
Launch Stripe CLI with PowerShell
Launch Stripe CLI with PowerShell

At this point, the CLI asks to press Enter to open a browser and pairs itself with the account in the Stripe Dashboard. After the Enter, we should see a screen like the following

Request from Stripe CLI to be paired with your Stripe account
Request from Stripe CLI to be paired with your Stripe account

Click on Allow access. Then, your screen changes and the access is granted.

Then, in your PowerShell, you see the Stripe CLI is configured for your application.

Stripe CLI is configured for your account
Stripe CLI is configured for your account

Now, you have to configure the Stripe CLI to receive the call from Stripe and forward the call to the controller StripeWebhook that we have just created. For that, in the PowerShell type

.\stripe.exe listen --forward-to https://localhost:7110/api/StripeWebhook
Stripe CLI is listenning for webhooks
Stripe CLI is listenning for webhooks

As you can see, this step gives you the webhook signing secret. Copy this key and paste in the configuration in the property WebhookSigningKey. Leave this PowerShell open. Now, run your server project. When the project is up and running, we are in the position to receive a trigger from Stripe. I want to try a successful payment. So, in a new PowerShell, type

.\stripe trigger payment_intent.succeeded

If you follow all steps, you receive a Trigger succeeded. You can add breakpoints in your code to check what your controller receives. In the Stripe CLI, you read the successful request.

Call the Stripe's trigger from PowerShell
Call the Stripe’s trigger from PowerShell

Now, in the open PowerShell, you have all details about the webhook call. You see that the event payment_intent.succeeded has 3 steps: charge.succeeded, payment_intent.succeded and payment_intent.created. You can write in the controller for each step the appropriate code to respond to your need.

Also, all the events are available in the Stripe Dashboard. Under the option Events, you can see all the calls to the webhook.

All trigger/transaction on the dashboard
All trigger/transaction on the dashboard

So, if you want to see more details about each event, just click on it. For example, I want to see all the details of the payment (first line in the above screenshot). I click on it and the result is in the following screnshot.

Transaction detail in the Stripe dashboard
Transaction detail in the Stripe dashboard

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.