In this post, I’ll show you how to accept a signed Meadow.Cloud webhook from ASP.NET Core. I’ll also show how to verify the signature against the payload, as this is a necessary verification step that ensures the web request came from Meadow.Cloud and not by a malicious actor.

Prerequisites

Among other things, you’ll need to have a Meadow board provisioned on a Meadow.Cloud account to follow along, since this is all about accepting a signed Meadow.Cloud webhook.

Accepting the webhook

To accept a signed Meadow.Cloud webhook, you’ll need to create an endpoint in your ASP.NET Core application that listens for the webhook. You can do this by creating a new controller and action method that accepts the webhook, or using a minimal api, which is the approach I’ll take here.

Visual Studio 2022 should make it fairly simple to create a new minimal api project, so I won’t go into details.

Reading the Meadow.Cloud documentation, you’ll find that Meadow.Cloud sends a POST, and signs the webhook with a secret key, and sends the signature in the X-MC-SIGNATURE-256 header. This signature is a hash of the payload and key if you’ve specified one. If you find that this header is empty, likely you’ve not specified a secret key in the Meadow.Cloud webhook configuration.

Here’s a minimal api that accepts a signed Meadow.Cloud webhook:


app.MapPost("webhook", ([FromHeader(Name ="X-MC-SIGNATURE-256")] string signature, [FromBody]string body) =>
{
    string ComputeHash(string input, string secret)
    {
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
        {
            byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(input));
            return Convert.ToHexString(hashBytes).ToLower();
        }
    }

    var secret = ""; // Your secret here
    var localhash = ComputeHash(body, secret);

    if (localhash.Equals(signature, StringComparison.OrdinalIgnoreCase))
    {
        try
        {
            // Signature matches, continue processing
            return Results.Ok();
        }
        catch (JsonException ex)
        {
            return Results.BadRequest(ex.Message);
        }
    }
    else
    {
        // Signature does not match, return error
        return Results.BadRequest("Invalid signature");
    }
});

Using ASP.NET model binding, the signature parameter is bound to the X-MC-SIGNATURE-256 header, and the body parameter is bound to the request body. The ComputeHash method computes the hash of the payload and the secret key, and the localhash variable stores the computed hash.

Keep in mind that your secret key should be stored in a secure location, and not hard-coded in your application as the example shows. That is outside the scope of this article to explain, but you might consider using a secure secret management system, such as Azure Key Vault, or AWS Secrets Manager.

I hope this article has been helpful to you. If you have any questions, the Wilderness Labs community is a great place to ask for help.