WooCommerce webhook c# - compare hash
Asked Answered
C

2

1

Can someone tell me how I can recreate the hash from WooCommerce webhook to compare with the "X-WC-Webhook-Signature" header hash from the request?

The documentation specifies the hash is generated from the 'payload', but I am unable to generate the same hash.

My API is .NET Core 3.1

First thing i tried:

var secret = "XXX";
var requestHash = Request.Headers["X-WC-Webhook-Signature"];
var generatedHash = "";
Stream byteContent = Request.Body;
byte[] keyByte = encoding.GetBytes(secret);
using(var hmacsha256 = new HMACSHA256(keyByte))
{
     byte[] hashmessage = hmacsha256.ComputeHash(byteContent);
     generatedHash = Convert.ToBase64String(hashmessage);
 }
 if(requestHash == generatedHash)
 {
     // Succes
 }

Second:

using(StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
    String json = await reader.ReadToEndAsync();
    var generatedHash = "";
    byte[] messageBytes = encoding.GetBytes(json);
    keyByte = encoding.GetBytes(secret);
    using(var hmacsha256 = new HMACSHA256(keyByte))
    {
        byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
        generatedHash = Convert.ToBase64String(hashmessage);
    }

    if(requestHash == generatedHash)
    {
        // Succes
    }
}
Caseworm answered 18/5, 2020 at 8:20 Comment(0)
M
1

This is what I have been using - doesn't required "AllowSynchronousIO" to be configured.

EDIT: I was using this in a Azure Function. In order to make this work in a ASP.NET core app, you need to change this:

var body = await request.Content.ReadAsStringAsync();

with this:

using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();

Make sure that the controller inherits from a ControllerBase.

/// <summary>
/// Verifies that the message has been signed with the shared secret
/// </summary>
/// <param name="message">Body of the post request</param>
/// <param name="sharedSecret">Secret passed on to Woocommerce</param>
/// <param name="signatureHeader">The value passed in through the x-wc-webhook-signature header</param>
/// <returns>Whether the message was signed with the shared secret</returns>
bool VerifySignature(string message, string sharedSecret, string signatureHeader)
{
    var signature = Convert.ToBase64String(
        HMACSHA256.HashData(
            Encoding.UTF8.GetBytes(sharedSecret).AsSpan(),
            Encoding.UTF8.GetBytes(message).AsSpan()));

    return signatureHeader == signature;
}

var sharedSecret = "secret";
var signatureHeader = request.Headers
    .Where(x => x.Key == "x-wc-webhook-signature")
    .First().Value.First();

// Important to NOT serialize/deserialize before validating.
// You must pass in the message as it is received.
// Serialization settings might change how the message was originally sent 
// and will invalidate the signature
var body = await request.Content.ReadAsStringAsync();

Assert.True(VerifySignature(body, sharedSecret, signatureHeader));
Monseigneur answered 4/8, 2023 at 21:20 Comment(1)
Request.Content is not available in the ControllerBase request? Not sure where you use this codeCaseworm
T
0

I had the same problem and here's what I did:

using System.Security.Cryptography;

if (Request.Headers.TryGetValue("X-WC-Webhook-Signature", out var headerValues))
{
    XWCWebhookSignature = headerValues.FirstOrDefault();
}

var encoding = new UTF8Encoding();
var key = "yourKeyValue";
var keyBytes = encoding.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(Request.Body);
var computedHashString = System.Convert.ToBase64String(computedHash);

if (XWCWebhookSignature != computedHashString)
{
    return Unauthorized();
}

Update: For this to work you will need to go to Startup.cs file and find "services.Configure" section. Add options.AllowSynchronousIO = true;

It should look like this:

services.Configure<IISServerOptions>(options =>
  {
     options.AllowSynchronousIO = true;
  });
Thriller answered 26/5, 2020 at 22:56 Comment(1)
Apparently I can't use 'hash.ComputeHash(Request.Body)'.. I'm getting the error: "Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead."Caseworm

© 2022 - 2025 — McMap. All rights reserved.