Hope someone can help me with this as I have been struggling with this for a few days. We are trying to run a SHA256 to create a base64 digest for a Shopify webhook to verify its authenticity. Basically, Shopify provides us with a x-shopify-hmac-sha256 header and response body.
We should derive our own HMAC and compare results. If our value is the same as the x-shopify-hmac-sha256 header then its authentic
DO NOT USE IN PRODUCTION The code shown below is just a rough demo. Read the code, understand what its doing, then write it robustly for production.
`
First setup an envar for your secret
So first of all we need to setup a envar for the SECRET so that your Shopify Sign Secret is not written in the code directly.
In my code below you’ll see “SECRET” which is an envar for this:
Second, create a new kv for testing
I created a new KV namespace for dumping output when Shopify makes a request. It’s only for testing and wouldn’t be kept in production code.
`
Third, the code:
addEventListener("fetch", event => {
event.respondWith(handleRequest(event))
})
function b64ToArrBuff(b64) {
const byteString = atob(b64);
let byteArray = new Uint8Array(byteString.length);
for(let i=0; i < byteString.length; i++)
byteArray[i] = byteString.charCodeAt(i);
return byteArray;
}
async function handleRequest(event) {
/* GET A CRYPTOKEY FROM THE SIGN SECRET PROVIDED BY SHOPIFY
* You can find this sign secret at the bottom of the notification webhooks
* from your stores settings notifications page. It's a 64 char long string.
*/
let cryptoKey = await crypto.subtle.importKey(
'raw',
(new TextEncoder()).encode(SECRET),
{
name: 'HMAC',
hash: {name: 'SHA-256'}
},
true,
['sign','verify']
);
/**
* GET THE PAYLOAD AS AN ARRAY BUFFFER
* This is the data which Shopify has sent you, its UTF-8 Stringified json
* but at the moment we need it as an ArrayBuffer.
*/
let requestPayload = await event.request.arrayBuffer();
/**
* CONVERT THE x-shopify-hmac-sha256 TO AN ARRAY BUFFER
* It was sent to us as a base64 string, we need it to be an array buffer.
*/
let signature = b64ToArrBuff( event.request.headers.get('X-Shopify-Hmac-Sha256') );
/**
* WE CAN NOW VERIFY
* Now that we have the requestPayload, the Shopify Signature, and our
* sign secret properly encoded, we can now verify if the request is legit.
*/
let result = await crypto.subtle.verify(
'HMAC',
cryptoKey,
signature,
requestPayload
);
// CONVERT THE requestPayload ARRAY BUFFER TO TEXT
let plaintext = (new TextDecoder()).decode(requestPayload);
await KVDump.put('X-SHOPIFY-HMAC-SHA256', event.request.headers.get('X-Shopify-Hmac-Sha256'));
await KVDump.put('THE REQUEST PAYLOAD', plaintext);
await KVDump.put('VERIFY SUCCESS RESULT', result ? 'IS VALID' : 'IS NOT VALID');
return new Response("Hello world")
}
At the end of my handleRequest function you can see I am doing some KV writing.
I’ll go into Shopify and press ‘Send test notification’