Hi y'all,
I'm setting up a webserver (Flask) to run as an endpoint for webhooks sent from AGOL but I'm having trouble verifying the secret when I receive the request.
So far, the endpoint works fine and does everything I want it to when I don't verify the secret that was sent with the webhook. However, I can't actually solve the secret due to the way Flask interprets the request from AGOL. The main issue being the x-esriHook-Signature header gets captured in the request.form object and not the request header. So what ends up happening is the signature is included with the data in an immutable multidict... which means if I try to create a new hmac, it's going to give me the wrong secret because the secret is included with the data. I've tried converting the immutable multidict data object to a regular python dict and popping the signature from the dict leaving just the request body, but that didn't help either.
I contacted technical support but they claimed that they don't support Flask which is hard to believe considering it's the second most popular Python web framework.
Has anyone else been able to verify webhook secret/signatures in Flask?
Solved! Go to Solution.
UPDATE:
The documentation is pretty sparse and all over the place regarding how to use the webhook secret.
The missing piece of info is that the hash is base64 encoded.
This link here is the blog post on creating web hooks in ArcGIS Online, but makes no mention of web hook secrets.
From that blog post, you could follow the link on creating a webhook in the REST API. This page tells you that a webhook secret: "If specified, the secret key will be used in generating the HMAC hex digest of value using sha256 hash function and is returned in the x-esriHook-Signature header."
Now at this point, you can Google ArcGIS Online webhooks and you might find yourself at this link of the REST API documentation that simply says this for the secret parameter: "A user-defined element that can be added to the payload to help authenticate the message on your receiver."
It's not until you find this page that mentions the webhook secrets are base64 encoded.
Here are some other things of note:
Here is some rough sample code to help anyone else out in the same situation:
import re
import hashlib
import hmac
import base64
bytes_data = request.get_data() # This has to be called before
#request.form
form = request.form
payload = re.split(b'payload=|&', bytes_data)[1]
secret = os.getenv('SECRET').encode('utf-8')
signature = 'sha256=' + base64.b64encode(hmac.new(secret, payload,
digestmod=hashlib.sha256).digest()).decode()
if hmac.compare_digest(signature, form['x-esriHook-Signature']):
#do something
UPDATE:
The documentation is pretty sparse and all over the place regarding how to use the webhook secret.
The missing piece of info is that the hash is base64 encoded.
This link here is the blog post on creating web hooks in ArcGIS Online, but makes no mention of web hook secrets.
From that blog post, you could follow the link on creating a webhook in the REST API. This page tells you that a webhook secret: "If specified, the secret key will be used in generating the HMAC hex digest of value using sha256 hash function and is returned in the x-esriHook-Signature header."
Now at this point, you can Google ArcGIS Online webhooks and you might find yourself at this link of the REST API documentation that simply says this for the secret parameter: "A user-defined element that can be added to the payload to help authenticate the message on your receiver."
It's not until you find this page that mentions the webhook secrets are base64 encoded.
Here are some other things of note:
Here is some rough sample code to help anyone else out in the same situation:
import re
import hashlib
import hmac
import base64
bytes_data = request.get_data() # This has to be called before
#request.form
form = request.form
payload = re.split(b'payload=|&', bytes_data)[1]
secret = os.getenv('SECRET').encode('utf-8')
signature = 'sha256=' + base64.b64encode(hmac.new(secret, payload,
digestmod=hashlib.sha256).digest()).decode()
if hmac.compare_digest(signature, form['x-esriHook-Signature']):
#do something