Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
I am working on verifying a webhook from shopify sent to an AWS endpoint and triggering a lambda function written in python 3.
I created the webhook in the notifications section of my shopify dashboard, and I am using the key that is available there as my SECRET variable. This is my verification function:
import hmac
import hashlib
import base64
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).digest() computed_hmac = base64.b64encode(digest) return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
and my lambda handler function:
import json
import verify_webhook
def lambda_handler(event, context): data = json.dumps(event) hmac_header = event['headers']['X-Shopify-Hmac-Sha256'] verified = verify_webhook.verify_webhook(data, hmac_header) print ('verified: ',verified)
My computed hmac and the provided header are consistently different from one another. I suspect that my data variable is incorrect, but I cant put my finger on the problem. Thanks in advance for any suggestions; your help is much appreciated!
Give you thumbs up for building in AWS Lambda. I stumbled upon this post have a read I believe it could help you as this is something specific to Lambda.
https://forums.aws.amazon.com/thread.jspa?threadID=228067
{ "json" : $input.json('$'), "rawBody" : "$util.escapeJavaScript($input.body)" }
-Sam
Hi guys,
Did anyone happen to get this working? I am trying to create a webhook and have it post to an AWS Lambda function via API Gateway ( same issue as above ), the HMAC will not validate. I was hoping someone had a code snippet or github of a working function to validate the webhook data sent from Shopify.
Thanks!
Alex
Hi Alex,
No, I'm still stuck. I think Sam is on the right track though. I am able to get the raw body, but can't figure out the full request. If you come across any other resources I'd love to see them though.
Thanks,
Cyrus
Thanks Sam,
I think this is the right idea, but I cant figure out how to return the full request. All of the resources for github validation just need to raw body, and I'm having trouble translating those examples. If you come across anything else please share!
Thanks again for your help,
Cyrus
I FINALLY got this HUGE pain in the ass to work tonight. I am attempting to show the steps here, I will also post some links to items that helped me.
In AWS > API Gateway, you have to create a Mapping Template, to intercept the incoming webhook from Shopify, then parse out the "raw body" vs the "JSON body", API Gateway will natively convert the webhook to JSON, when it does, it messes up the raw body and adds several things, this throws off your HMAC check.
1. Set up or create a Lambda Function and tie to to an API Gateway REST API ( not HTTP Passthrough ). Map the new REST API to a POST method. Once you are done you should have something that looks like this ( your name will differ ).
2. Click on "POST" method under your new resource.
3. In the POST method, you set Integration type to Lambda Function. Leave the rest as default, scroll down to the bottom of the page.
4. Under the Mapping Template, click the top radio button, then paste in the template I am supplying below ( this template parses out the HTTP POST Header, Body, RAW BODY, HTTP Parameters, and Query strings into seperate variables that you can consume inside your Lambda Functions.
5. The complete script is below:
{
"body" : $input.json('$'),
"rawbody": "$util.escapeJavaScript($input.body)",
"headers": {
#foreach($header in $input.params().header.keySet())
"$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end
#end
},
"method": "$context.httpMethod",
"params": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
},
"query": {
#foreach($queryParam in $input.params().querystring.keySet())
"$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
#end
}
}
6. Make SURE you Save, then click Actions > Deploy API ( I goofed and forgot to redeploy the API after making changes my first go around ).
7. Head to your Lambda function. I am showing how to access the various variables here:
import json
import hmac
import hashlib
import base64
#Shopify Secret Secret Key used for verification
SECRET = 'ENTER YOUR SECRET KEY THAT SHOPIFY DISPLAYS BESIDE YOUR WEBHOOK'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
def lambda_handler(event, context):
print('Entering into the Lambda Function now.')
print('......................................')
print('The JSON body received from the API Gateway after passing through the mapping template is: ')
print(json.dumps(event))
print('The RAW body received from the API Gateway after passing through the mapping template is: ')
print(event['rawbody'])
print('The Header is: ')
print(event['headers'])
print('The Headers Shopify X-Shopify-Hmac-Sha256 signature is: ')
print(event['headers']['X-Shopify-Hmac-Sha256'])
print('Completed the printout of the passed in values.')
rawbodydata = event['rawbody']
hmac_header = event['headers']['X-Shopify-Hmac-Sha256']
verified = verify_webhook(rawbodydata, hmac_header)
if not verified:
print('Did not pass Shopify HMAC validation!')
return {
'statusCode': 401,
'body': json.dumps('Did not pass Shopify HMAC validation!')
}
if verified:
print('OOOOOHHH YEAH!!! Passed Shopify HMAC validation!')
#
# YOUR APP LOGIC GOES HERE!!!!
#
return {
'statusCode': 200,
'body': json.dumps('OOOOOHHH YEAH!!! Passed Shopify HMAC validation!')
}
I am going to continue working on this to parse out the variables/data I need. I'm willing to barter with anyone if they are interested?? I am not a programmer by trade, this was just me being persistant and knowing a bit about networking/HTTP Requests and a LOT of screwing around with AWS API Gateway. I did find a VERY good Youtube series on API Gateway here:
His Really good video's start from about video 20 and go up to 38. These cover all the basics of API Gateway.
https://www.youtube.com/watch?v=K1E8UM6IdNY&list=UUKdYWKHTqJdJs4yQb0M7o3Q
https://www.youtube.com/watch?v=2Z-Utw_xl4c
Hope that helps someone out....
Alex
One last thing... if you want to see the actual values real time, look at the CloudWatch logs as you execute your webhook from Shopify, that was how I was troubleshooting. You could also use PostMan and emulate a Shopify webhook post.
@AlexH316 that is damn good work! Kudos to you. Frankly using API Gateway and Lamda functions is way cool.
All the best,
Sam
Sam,
Thank you for kind words. Quick question, I noticed your website for achieveapplabs.com, you guys do Shopify theme development? Do you happen to have any example sites I could take a look at? I may have an opp for you.
Thanks!
Alex
Hi @AlexH316 ,
We haven't ventured into Shopify themes yet, but we know we will eventually. Our focus is on app development at the moment.
Regards,
Sam
Alex,
Thanks a lot for sharing this, saved my day !!. Much appreciated.
import crypto from 'crypto';
// Shopify Secret Secret Key used for verification
const SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
function verifyWebhook(data, hmacHeader) {
//const computedHmac = crypto.createHmac('sha256', SECRET).update(data).digest('base64');
//return crypto.timingSafeEqual(Buffer.from(computedHmac), Buffer.from(hmacHeader));
return crypto
.createHmac("sha256", SECRET)
.update(data.toString(), "utf8", "hex")
.digest("base64");
}
export async function handler(event, context) {
console.log('Entering into the Lambda Function now.');
console.log('......................................');
console.log('The JSON body received from the API Gateway after passing through the mapping template is: ');
console.log(JSON.stringify(event));
console.log('The RAW body received from the API Gateway after passing through the mapping template is: ',event.rawBody);
console.log('The Header is: ',event.headers);
console.log('The Headers Shopify X-Shopify-Hmac-Sha256 signature is: ',event.headers['X-Shopify-Hmac-Sha256'] );
console.log('Completed the printout of the passed in values......................');
const rawBodyData = event.rawbody;
const hmacHeader = event.headers['X-Shopify-Hmac-Sha256'];
const verified = verifyWebhook(rawBodyData, hmacHeader);
if (!verified) {
console.log('Did not pass Shopify HMAC validation!');
return {
statusCode: 401,
body: JSON.stringify('Did not pass Shopify HMAC validation!')
};
}
console.log('OOOOOHHH YEAH!!! Passed Shopify HMAC validation!');
//
// YOUR APP LOGIC GOES HERE!!!!
//
return {
statusCode: 200,
body: JSON.stringify('OOOOOHHH YEAH!!! Passed Shopify HMAC validation!')
};
}
Thanks so very much for this post!! I got it working with node.js so just adding my own example in here. ChatGPT did some funky stuff with your Python 🙂
Your template worked perfectly as is.
Also, a lot of tears on EventBridge not being able to accept a query string or anything like it. You don't have to validate there, but you also can't pass any additional info like with API Gateway.
Nice work man!
Shopify advises that there is no need to verify a HMAC from EventBridge. I also wasted a stupid amount of time on this.