cancel
Showing results for 
Search instead for 
Did you mean: 

HMAC SHA-512 hash not matching in webhook callback

Hello,

 

I recently began working on integrating with the new Webhook API to subscribe to events.  This is being integrated into an existing ASP.NET solution written in C#.  I would like to validate webhook messages using the X-ANET-Signature header, but so far have not been able to produce a matching signature in my test setup.  My code is as follows:

 

        protected void Page_Load(object sender, EventArgs e)
        {

                string keytext = "MY AUTHORIZE.NET SIGNATURE KEY";

                byte[] key = HexDecode(keytext);

                HMACSHA512 hmacsha = new HMACSHA512(key);

                byte[] hash = hmacsha.ComputeHash(Request.InputStream);

                string computedHash = HashEncode(hash);

                 //compare with the X-ANET-Signature, they don't match
        }

 

        private static byte[] HexDecode(string hex)
        {
            var bytes = new byte[hex.Length / 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = byte.Parse(hex.Substring(i * 2, 2), NumberStyles.HexNumber);
            }
            return bytes;
        }

        private static string HashEncode(byte[] hash)
        {
            return BitConverter.ToString(hash).Replace("-", "").ToLower();
        }

 

 

Any guidance would be much appreciated!

bschuller
Member
7 REPLIES 7

Hi @bschuller,

 

I can't tell exactly from your code where the problem might be, but here's some things to check.

 

Is Request.InputStream getting the body, the whole body, and nothing but the body? Add a line to output that and see if it's the entire JSON object, or something less/more.

 

Take whatever you've output from Request.InputStream and calculate the HMAC for it outside your code. Using a website like http://www.freeformatter.com/hmac-generator.html, you can put in the body that you've retrieved, and put in your signature key as the key. If you get a matching HMAC from that site, then you know your code is either mangling the signature key in the HexDecode function, or mangling the message body, or doing something else bad. If you don't get a matching HMAC from the web site, then maybe what you retrieved as the message body isn't complete or has something extra. Or, you're just using the wrong signature key.

 

Basically, it's just a strategy of breaking down what you're doing into steps, and checking the output at each point against a known working implementation.

 

Here's an example transaction I just tried. The X-Anet-Signature: header is:

sha512=9607C69D2734514554CE13D923D19939563F2FD6567D239F49F0DF0D08865632A0FB98061B4620CC52C47BB8BF6E2B69A15A67E4C98E81D7D65111076C515C6A

The body of the notification is:

{"notificationId":"f813b98f-3552-4eeb-ba40-acb4704eeba3","eventType":"net.authorize.payment.authcapture.created","eventDate":"2017-03-08T17:35:37.155999Z","webhookId":"63d6fea2-aa13-4b1d-a204-f5fbc15942b7","payload":{"responseCode":1,"authCode":"8VTPDC","avsResponse":"Y","authAmount":10.00,"entityName":"transaction","id":"60019392075"}}

My signature key is:

 

2679A5D5785C9B26CF921E1754765CDDB265C9C88400786923E5343B315954FAFB4B7BF608818F6A21766741E8637DC206F286C725908C777ED4127D8562163D

 

Using those values, I'm able to generate a HMAC that matches the one returned in the header.

 

Let us know how it works out.

Aaron
All Star

Hi Aaron,

 

Thank you for the reply.  I did get things sorted, so I thought I'd go ahead and post the details of the problem in case it helps anyone else.  I had already followed the various debugging steps you mentioned, but it helped to go over to the hmac-generator page you sent, since it showed me the problem had to be in my conversion of the signature key into a byte array.  I had mistakenly thought the signature key was to be read as pairs of characters making up a single hex digit, so I was parsing it like "26", "79", "A5"...etc (using your signature key as an example).  So I was getting a byte array of length 64 instead of 128.  Needed to just take the text of the key, do a simple "Encoding.ASCII.GetBytes(keytext)" instead, and it worked from that point forward.

 

Appreciate the help!

This post has been helpful, but I'm still stuck. I've spent far too long trying to figure this same thing out and I'm really close, thanks to your help. I'm just having a hard time with the final syntax. I'm working in VB and I'm confusing things I think with the conversion from C#.

 

Could you please post your final code?

 

Thanks!

 

- Jason

We do have a github project done by a developer for supporting  webhooks 

 

https://github.com/dns12345/AuthNet.WebHooks

 

Hope it helps !!





Send feedback at developer_feedback@authorize.net

 


@anuragg29 wrote:

We do have a github project done by a developer for supporting  webhooks 

 

https://github.com/dns12345/AuthNet.WebHooks

 

Hope it helps !!



Thanks for your help... I checked out the source code on github, which is great if you speak C#.  =) It did give me some ideas of how to implement things.

 

For struggling VB peeps trying to make sense of it all, I finally came up with some code after days of wrapping my head around it all. There may be a better/easier way to do this, however what I've got here seems to work. I hope this helps someone. Feel free to make it better.

 

Here are the steps:

1) From your Authorize.net account, create your Signature Key. (Account Settings > API Credentials & Keys). Save this somewhere... you're going to need it.

2) Create your webhook endpoint URL. (Business Settings > Webhooks). Make sure you keep it INACTIVE. When it's INACTIVE, a "Test Webhook" button is visible and enabled. When it's ACTIVE, the button disappears and you can't test it.

3) Create your webhook file in your application and copy and paste the code below.

4) Edit the code below where it reads <<put your Signature Key here>> and paste your Signature Key from Step 1 above.

5) Put your email address where it says <<your email address>> in both places. 

6) You can now TEST your webhook and have your code email you to let you know it succeeded or failed. I simply emailed the result to myself so I could verify the X-ANET signature matched the generated hash.

7) You're welcome. ;-)

 

Imports System.IO

Imports System.Net.Mail
Imports System.Security.Cryptography

 

Partial Public Class webhook
Inherits System.Web.UI.Page


Public Shared Function ByteToString(ByVal buff As Byte()) As String
Dim sbinary As String = ""
For i As Integer = 0 To buff.Length - 1
sbinary += buff(i).ToString("X2")
Next

Return (sbinary)
End Function

 

Sub SubSendData(ByVal strEmail As String, ByVal strSubject As String, ByVal strBody As String)

'Add the constant ToAddress
Const ToAddress As String = "<<your email address>>"

'(1) Create the MailMessage instance (from/to)
Dim mm As New MailMessage(strEmail, ToAddress)


'(2) Assign the MailMessage's properties
mm.Subject = strSubject
mm.Body = strBody
mm.IsBodyHtml = False

'(3) Create the SmtpClient object
Dim smtp As New SmtpClient

'(4) Send the MailMessage (will use the Web.config settings)
'On Error Resume Next
smtp.Send(mm)

End Sub

 


Protected Sub form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

 

' Load Header collection into NameValueCollection object.
Dim coll As NameValueCollection
coll = Request.Headers

Dim loop1, loop2 As Integer
Dim arr1(), arr2() As String

Dim strSha512 As String = ""

 

' Put the names of all keys into a string array.
arr1 = coll.AllKeys
For loop1 = 0 To arr1.GetUpperBound(0)
arr2 = coll.GetValues(loop1)


' Get all values under this key.
For loop2 = 0 To arr2.GetUpperBound(0)

 

'the sha512 should be the last one in the array
'but to be sure let's check and exit when we find it


If InStr(arr2(loop2), "sha512") <> 0 Then

 

strSha512 = Server.HtmlEncode(arr2(loop2))

 

Exit For


End If

 

Next loop2


Next loop1

 

 

 

If strSha512 <> "" Then 'we found the X-ANET signature so continue

 

strSha512 = Replace(strSha512, "sha512=", "")

 

' now get the incoming stream body
Dim strInputStream As String
Dim streamReceive As Stream = Request.InputStream
Dim reader As StreamReader = New StreamReader(streamReceive, Encoding.UTF8)
strInputStream = reader.ReadToEnd

 

 

' calculate the fingerprint
Dim strKeyText As String = "<<put your Signature Key here>>"
Dim encASCII As ASCIIEncoding = New ASCIIEncoding()
Dim bytKeyText As Byte() = encASCII.GetBytes(strKeyText)
Dim hmacsha As HMACSHA512 = New HMACSHA512(bytKeyText)
Dim bytInputStream As Byte() = encASCII.GetBytes(strInputStream)
Dim bytHashInputStream As Byte() = hmacsha.ComputeHash(bytInputStream)
Dim strComputedHash As String = ByteToString(bytHashInputStream)

 

'now we have both the gateway X-ANET Signature (strSha512) , and our client generated value (strComputedHash) to compare

 

SubSendData("<<your email address>>", "webhook fired", strInputStream & vbNewLine & vbNewLine & strSha512 & vbNewLine & vbNewLine & strComputedHash)

 

If strSha512 = strComputedHash Then
'good to go so then do what we need

 

 

 

Else ' no match so do something else

 

End If


Else 'X-ANET signature (strSha512) is not found so abort


End If


End Sub


End Class

 

 

 

 

private static string ByteToString(byte[] buff)
{
string sbinary = "";
for(var i=0; i<=buff.Length-1; i++)
{
sbinary += buff[i].ToString("X2");
}
return sbinary;

 

I ended having to use this instead of Convert.ToBase64String(payloadHashbytes).

byte[] keybytes = Encoding.UTF8.GetBytes(secret);

System.Security.Cryptography.HMACSHA512 hmac1 = new System.Security.Cryptography.HMACSHA512(keybytes);
byte[] payloadbytes = Encoding.UTF8.GetBytes(payload);
byte[] payloadhashbytes = hmac1.ComputeHash(payloadbytes);
//string hmacString = Convert.ToBase64String(payloadhashbytes); //this is not working...had to use below ByteToString method
string hmacString = ByteToString(payloadhashbytes);
return hmacString;

byte[] keybytes = Encoding.UTF8.GetBytes(secret);

byte[] payloadbytes = Encoding.UTF8.GetBytes(payload);
byte[] payloadhashbytes = hmac1.ComputeHash(payloadbytes);
//string hmacString = Convert.ToBase64String(payloadhashbytes); //this is not working...had to use below ByteToString method
string hmacString = ByteToString(payloadhashbytes);
return hmacString;

This is C# version of Janga's code above which is working for me.

private static string ByteToString(byte[] buff)
{
string sbinary = "";
for(var i=0; i<=buff.Length-1; i++)
{
sbinary += buff[i].ToString("X2");
}
return sbinary;
}