I am trying to verify the webhook received from Shopify but the Hmac verification fails.
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/productCreation', methods=['POST'])
def productCreation():
data = request.data
verified = verify_webhook(
data, request.headers.get('X-Shopify-Hmac-SHA256'))
if(verified):
return ("Success", 200)
return("Integrity error", 401)
Getting error as
hash = hmac.new(SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256)
AttributeError: 'bytes' object has no attribute 'encode'
Can anyone help with this? I am developing a Flask app for this.
You´re getting a attribute error, means your body object is not a string (encode() is working for string like objects) as mentioned in the error message it is a 'bytes' like object. Remove the
.encode('utf-8') from body.
Update March 2022
It seems like Shopify has updated their Flask example with the same changes I suggested below.
Original answer
I got the same error when using Shopify's Flask example
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET, data.encode('utf-8'), hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)
To get it to work I had to modify verify_webhook by:
Encoding SECRET since hmac.new() expects the key in bytes and not as a string.
Not encoding data since Flask's response.get_data already returns an encoded bytestring.
The final code
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)
Related
The bounty expires in 12 hours. Answers to this question are eligible for a +50 reputation bounty.
Gurkenkönig wants to draw more attention to this question.
I can't manage to validate the X-Hub-Signature-256 for my meta / whatsapp webhook in flask successfully.
Can anyone tell me where the error is or provide me with a working example?
import base64
import hashlib
import hmac
import os
from dotenv import load_dotenv
from flask import Flask, jsonify, request
from werkzeug.middleware.proxy_fix import ProxyFix
load_dotenv()
API_SECRET = os.environ.get('API_SECRET')
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data,
digestmod=hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_recieved.encode('utf-8'))
#app.route("/whatsapp", methods=["GET", "POST"])
def whatsapp_webhook():
if request.method == "POST":
try:
data = request.get_data()
if not verify_webhook(data, request.headers.get('X-Hub-Signature-256')):
return "", 401
except Exception as e:
print(e)
return "", 500
return jsonify({"status": "success"}, 200)
This seems to work:
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
If you run this test:
import base64
import hashlib
import hmac
import os
API_SECRET = 'very_secret_key'
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
print(verify_webhook('Good morning', 'sha256=63e447ebe2bb46cb621972656087950c9d3a437caa61ece01f18094cc99f5a16'))
It returns True as expected:
$ python3 xhub_new.py
True
Im trying to get market info via the REST API. This is my code:
import requests
import hmac
import hashlib
def getServerTime():
return requests.get("https://api.binance.com/api/v3/time").json()["serverTime"]
def getSignature():
query_string = f"timestamp={getServerTime()}"
secret = "--------MY SECRET KEY-----------"
return hmac.new(secret.encode("utf-8"), query_string.encode("utf-8"), hashlib.sha256).hexdigest()
def getApiKey():
return "-------MY API KEY------------"
def getAllCoinsInformation():
parameters = {'timestamp':getServerTime(),'signature':getSignature()}
headers = {"Content-Type": "application/json;","X-MBX-APIKEY":getApiKey()}
return requests.get('https://api.binance.com/sapi/v1/capital/config/getall',headers=headers, params=parameters).json()
print(getAllCoinsInformation())
When i run program return is {'code': -1022, 'msg': 'Signature for this request is not valid.'}
So how to create valid signature in python. Any kind of suggestions can be accepted.
I'm trying to verify that the Webhook received is coming from Shopify. They have this doc, but it doesn't work (getting type errors).
Here's what I have so far. It produces no errors, but the verify_webhook function always returns false.
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.get_data()
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
What am I doing wrong?
Answer:
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.data # NOT request.get_data() !!!!!
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
Issue was in the data = request.get_data() line.
Im trying to create a script, either by Python or PHP, that can receive JSON from Dialogflow webhooks.
The data value will be from resolvedQuery.
I want to parse the data then send to the below URL:
https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?text=***JSON GOES HERE ***&deviceId=18c972b753ad&apikey=5b48aed7
The data from resolvedQuery needs to be sent to the about URL where *JSON GOES HERE * is.
Here is the Python code I have been trying:
from flask import Flask
from flask import request
from flask import make_response
import json
import logging as l
import requests
app = Flask(__name__)
#app.route('/')
def hello():
"""Return a friendly HTTP greeting."""
l.info("reached the hello() module...")
return 'Hello Beautiful World!\n'
#app.route('/apiai', methods=['POST'])
def apiai_response():
requests_session = requests.session()
requests_session.headers.update({'Content-Type': 'application/json'})
requests_session.headers.update({'charset':'utf-8'})
post_data = '[{ "resolvedQuery": "" }]'
requests_response = requests_session.post(url="https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?text=***JSON GOES HERE ***&deviceId=18c972b753ad&apikey=5b48aed7", data=post_data)
print requests_response.content
#app.errorhandler(404)
def page_not_found(e):
"""Return a custom 404 error."""
return 'Sorry, nothing at this URL.', 404
I would try something like this:
import json
import requests
json_string = json.dumps({"hello": "world"})
these_params = {"text": json_string,
"deviceId": "18c972b753ad&apikey=5b48aed7"}
this_url = "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush"
r.get(url=this_url, params=these_params)
notice it is a get request and not a post request
It is a matter of opinion however using url encoded params on a GET request differ in how stuff gets read. As far as the data field is concerned on a post request, the handling is a bit different.
On a get request it will look something like part of the url as :
text=%7B%22hello%22%3A%22world%22%7D
Where as a post request the server will try to read in a application/json encoded body which expects the "data" to be valid json text.
also note:
How to urlencode a querystring in Python?
I am trying to build a Microsoft Teams chat bot using Flask, following the instructions on how to build custom bots. However I am unable to verify the HMAC auth which I really want for security.
Based on guides and documentation I've found I am using the following minimial testing app trying to calculate a HMAC for the incoming request. (Bot name and description DevBot and the key/security_token below for testing).
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(security_token, msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: Verify signature = Authorization header HMAC here
return jsonify({
'type' : 'message',
'text' : "Auth header: {0} <br>Calculated HMAC: {1}".format(request.headers.get('Authorization'), signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Upon sending the message #DevBot test I get the following hashes back in the reply from the bot, but they aren't matching as expected:
Auth header: HMAC LuDmz97y/Z2KWLIZ1WZASz3HlOEtDCwk5/lL/fK8GqM=
Calculated HMAC: eaxTdJSLuU3Z4l94bxFiWvsBhjNG9SPxwq/UHeR7KcA=
Any ideas or pointers? I've been trying all sorts of stuff with encoding but I have a feeling that Flask might be doing something that modifies the request body or something?
edit 1: small clarification
edit 2: full Flask app example
edit 3: sample bot details, input and output examples
After lots of trial and error and trying to reproduce the C# code example from MS I managed to solve it myself. Here's the solution:
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Reply
data = request.get_json()
channel = data['channelId']
message_type = data['type']
sender = data['from']['name']
message_format = data['textFormat']
message = data['text']
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(base64.b64decode(security_token), msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: verify that HMAC header == signature
return jsonify({
'type' : 'message',
'text' : "auth header: {0} <br>hmac: {1}".format(request.headers.get('Authorization').split(' ')[1], signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Another option rather than interfacing directly with Microsoft Teams may be to use the Microsoft Bot Connector API.
https://docs.botframework.com/en-us/restapi/connector/
I have a bot working with Microsoft Teams using https://github.com/Grungnie/microsoftbotframework which is validating the JWT that is sent from Microsoft.