Solved

Webhook Signature Verification

Clmkx
Shopify Partner
3 1 2

I am trying to verify incoming webhook signatures in C# using the following code.

 

public async Task<bool> Validate(string sharedSecretKey, HttpRequest request)

{

    request.EnableBuffering();
 
    using var streamReader = new StreamReader(request.Body, leaveOpen: true);

    // Read out the body - this is required to generate the HMAC signature.
    string requestBody = await streamReader.ReadToEndAsync();
    request.Body.Position = 0; // Reset position.
 
    string calculatedSignature = "";
    string hmacHeader = request.Headers["X-Shopify-Hmac-SHA256"];
        
    byte[] secretBytes = Encoding.UTF8.GetBytes(sharedSecretKey);
    byte[] requestBodyBytes = Encoding.UTF8.GetBytes(requestBody);
 
    using (HMACSHA256 hmac = new HMACSHA256(secretBytes))
    {
        byte[] signatureBytes = hmac.ComputeHash(requestBodyBytes);
        calculatedSignature = Convert.ToBase64String(signatureBytes);
    }
 
    return hmacHeader == calculatedSignature;
}
 
However, the value outputted in "calculatedSignature" does not match the value provided in the header of the webhook request. I am confident I am using the correct secret value (coming from the "Client secret" section of the Client credentials page against the integrated app). I have tried multiple different way of generating the signature but getting the same output each time which never matches the value coming from Shopify. Any help would be appreciated.
Accepted Solution (1)
Clmkx
Shopify Partner
3 1 2

This is an accepted solution.

Hi,

 

Managed to resolve the issue, it seems I needed to change the way I was getting the value from the hmac header.

 

private async Task<bool> ValidateShopifySignature(HttpRequest req, string clientSecret)
{
    Microsoft.Extensions.Primitives.StringValues header;
    req.Headers.TryGetValue("X-Shopify-Hmac-Sha256", out header);

    string? hmacHeader = header.FirstOrDefault();

    if (!string.IsNullOrWhiteSpace(hmacHeader))
    {
        var sharedSignatureBytes =  Encoding.UTF8.GetBytes(clientSecret);
        using var hmac = new HMACSHA256(sharedSignatureBytes);


        // Copy the request body to a memory stream then convert it to a byte[].
        using MemoryStream dataStream = new();
        await req.Body.CopyToAsync(dataStream);

        var dataBytes = dataStream.ToArray();


        // Compute a hash of the body based on the signature.
        var generatedHmacHashBytes = hmac.ComputeHash(dataBytes);
        var generatedSignature = Convert.ToBase64String(generatedHmacHashBytes);


        // Compare that signature to the one that Shopify generated and sent over.
        return hmacHeader == generatedSignature;
    }
    else
    {
        return false;
    }
}

 

View solution in original post

Replies 5 (5)

AlexGout
Shopify Staff
9 0 9

Hey, sorry to hear about this problem. Before digging further can you please checkout this issue and see if the suggestions made here solve the problem you're facing?

 

Thanks,

Alex

To learn more visit the Shopify Help Center or the Community Blog.

Clmkx
Shopify Partner
3 1 2

Hi, I am confident the value being used for the secret does not contain any spaces or quotes and it the same value used during the integration creation. My only idea at the moment is the encoding of the request but the docs do not provide a C# example and I have tried many different approaches to this and get the same outcome.

 

Thanks

AlexGout
Shopify Staff
9 0 9

Hi,

 

your code looks good. The only things I can think of that could be wrong: 

 

- you're using the wrong key

- you're using the wrong encoding (i.e. it's not UTF-8 for some reason)

Maybe you could debug and actually look incoming body. 

To learn more visit the Shopify Help Center or the Community Blog.

Clmkx
Shopify Partner
3 1 2

This is an accepted solution.

Hi,

 

Managed to resolve the issue, it seems I needed to change the way I was getting the value from the hmac header.

 

private async Task<bool> ValidateShopifySignature(HttpRequest req, string clientSecret)
{
    Microsoft.Extensions.Primitives.StringValues header;
    req.Headers.TryGetValue("X-Shopify-Hmac-Sha256", out header);

    string? hmacHeader = header.FirstOrDefault();

    if (!string.IsNullOrWhiteSpace(hmacHeader))
    {
        var sharedSignatureBytes =  Encoding.UTF8.GetBytes(clientSecret);
        using var hmac = new HMACSHA256(sharedSignatureBytes);


        // Copy the request body to a memory stream then convert it to a byte[].
        using MemoryStream dataStream = new();
        await req.Body.CopyToAsync(dataStream);

        var dataBytes = dataStream.ToArray();


        // Compute a hash of the body based on the signature.
        var generatedHmacHashBytes = hmac.ComputeHash(dataBytes);
        var generatedSignature = Convert.ToBase64String(generatedHmacHashBytes);


        // Compare that signature to the one that Shopify generated and sent over.
        return hmacHeader == generatedSignature;
    }
    else
    {
        return false;
    }
}

 

AlexGout
Shopify Staff
9 0 9

Hi,

 

Thanks for letting us know!

To learn more visit the Shopify Help Center or the Community Blog.