Skip to content

Commit 42f0b56

Browse files
committed
feat: ASP.NET Core inbound webhooks example
1 parent bae0f72 commit 42f0b56

File tree

8 files changed

+673
-0
lines changed

8 files changed

+673
-0
lines changed

dotnet/.gitignore

Lines changed: 484 additions & 0 deletions
Large diffs are not rendered by default.

dotnet/inbound/Program.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Security.Cryptography;
2+
using System.Text;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
6+
var app = builder.Build();
7+
8+
const string HOOKDECK_SIGNATURE_HEADER = "X-Hookdeck-Signature";
9+
const string HOOKDECK_WEBHOOK_SECRET_CONFIG_KEY = "inbound:HookdeckWebhookSecret";
10+
11+
string WEBHOOK_SECRET = builder.Configuration[HOOKDECK_WEBHOOK_SECRET_CONFIG_KEY] ?? string.Empty;
12+
13+
static bool VerifyHmacWebhookSignature(HttpContext context, string webhookSecret, string rawBody)
14+
{
15+
if(string.IsNullOrEmpty(webhookSecret))
16+
{
17+
Console.WriteLine("WARNING: Missing webhook secret. Skipping verification.");
18+
return true;
19+
}
20+
21+
string? hmacHeader = context.Request.Headers[HOOKDECK_SIGNATURE_HEADER].FirstOrDefault();
22+
23+
if (string.IsNullOrEmpty(hmacHeader))
24+
{
25+
Console.WriteLine("Missing HMAC headers");
26+
return false;
27+
}
28+
HMACSHA256 hmac = new(Encoding.UTF8.GetBytes(webhookSecret));
29+
string hash = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody)));
30+
31+
return hash.Equals(hmacHeader);
32+
}
33+
34+
app.MapPost("/{**path}", async (string? path, HttpContext context) =>
35+
{
36+
using StreamReader reader = new StreamReader(context.Request.Body);
37+
string rawBody = await reader.ReadToEndAsync();
38+
39+
bool verified = VerifyHmacWebhookSignature(context, WEBHOOK_SECRET, rawBody);
40+
if(!verified)
41+
{
42+
return Results.Unauthorized();
43+
}
44+
45+
Console.WriteLine(new
46+
{
47+
webhook_received = DateTime.UtcNow.ToString("o"),
48+
body = rawBody
49+
});
50+
51+
return Results.Json(new {
52+
STATUS = "ACCEPTED"
53+
});
54+
});
55+
56+
app.UseRouting();
57+
58+
app.Run();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:18206",
8+
"sslPort": 0
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"applicationUrl": "http://localhost:5176",
17+
"environmentVariables": {
18+
"ASPNETCORE_ENVIRONMENT": "Development"
19+
}
20+
},
21+
"IIS Express": {
22+
"commandName": "IISExpress",
23+
"launchBrowser": true,
24+
"environmentVariables": {
25+
"ASPNETCORE_ENVIRONMENT": "Development"
26+
}
27+
}
28+
}
29+
}

dotnet/inbound/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Quickstart: Inbound webhooks with C# and ASP.NET Core
2+
3+
An example application demonstrating receiving a webhook with C# and
4+
[ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/?view=aspnetcore-6.0).
5+
6+
- Follow the [Hookdeck Inbound Webhook Quickstart](https://hookdeck.com/docs/receive-webhooks)
7+
- Check out the [Hookdeck docs](https://hookdeck.com/docs?ref=github-quickstarts)
8+
9+
## Before you begin
10+
11+
You'll need:
12+
13+
- The [Hookdeck CLI](https://hookdeck.com/docs/cli?ref=github-quickstarts-dotnet)
14+
- The [.NET CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/)
15+
16+
## Get the code
17+
18+
```sh
19+
git clone https://github.com/hookdeck/quickstarts hookdeck-quickstarts
20+
cd hookdeck-quickstarts/dotnet/inbound
21+
```
22+
23+
## Configuration (optional)
24+
25+
To use webhook verification, get your webhook secret from the **Settings** -> **Secrets** section
26+
of your Hookdeck Project.
27+
28+
```sh
29+
dotnet user-secrets init --project "."
30+
dotnet user-secrets set "inbound:HookdeckWebhookSecret" "YOUR-WEBHOOK-SECRET" \
31+
--project "."
32+
```
33+
34+
## Run the app
35+
36+
```sh
37+
dotnet run
38+
```
39+
40+
Run the Hookdeck localtunnel using the Hookdeck CLI:
41+
42+
```sh
43+
hookdeck listen 5176 inbound-dotnet
44+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"DetailedErrors": true,
3+
"Logging": {
4+
"LogLevel": {
5+
"Default": "Information",
6+
"Microsoft.AspNetCore": "Warning"
7+
}
8+
}
9+
}

dotnet/inbound/appsettings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
},
8+
"AllowedHosts": "*"
9+
}

dotnet/inbound/inbound.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<UserSecretsId>1c4656e0-9527-4aa3-ad5d-5ecda11c5c8f</UserSecretsId>
8+
</PropertyGroup>
9+
10+
</Project>

dotnet/quickstarts.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.002.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{571A72E4-73CF-495F-B301-D9A0A445E0BD}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "inbound", "inbound\inbound.csproj", "{4691A94B-A56A-4965-AB30-882F2DEA82C7}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{4691A94B-A56A-4965-AB30-882F2DEA82C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{4691A94B-A56A-4965-AB30-882F2DEA82C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{4691A94B-A56A-4965-AB30-882F2DEA82C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{4691A94B-A56A-4965-AB30-882F2DEA82C7}.Release|Any CPU.Build.0 = Release|Any CPU
20+
EndGlobalSection
21+
GlobalSection(SolutionProperties) = preSolution
22+
HideSolutionNode = FALSE
23+
EndGlobalSection
24+
GlobalSection(NestedProjects) = preSolution
25+
{4691A94B-A56A-4965-AB30-882F2DEA82C7} = {571A72E4-73CF-495F-B301-D9A0A445E0BD}
26+
EndGlobalSection
27+
GlobalSection(ExtensibilityGlobals) = postSolution
28+
SolutionGuid = {B6AA535C-137D-4116-AA35-1C895412D42A}
29+
EndGlobalSection
30+
EndGlobal

0 commit comments

Comments
 (0)