How to Validate a Xero webhook payload with HMACSHA256 python 3
Asked Answered
S

2

6

Based on the instructions here (https://developer.xero.com/documentation/webhooks/configuring-your-server) for setting up and validating the intent to receive for the Xero webhook.

The computed signature should match the signature in the header for a correctly signed payload.

But, using python 3, the computed signature doesn't match the signature in the header in any way at all. Xero would send numerous requests to the subscribing webhook url for both correctly and incorrect. In my log, all those requests returned as 401. So, below is my test code which also proved to not match. I don't know what is missing or I what did wrong. Don't worry about the key been show here, i have generated another key but this was the key assigned to me to use for hashing at this point. based on their instruction, running this code should make the signature match one of the headers. But not even close at all.

XERO_KEY = 
"lyXWmXrha5MqWWzMzuX8q7aREr/sCWyhN8qVgrW09OzaqJvzd1PYsDAmm7Au+oeR5AhlpHYalba81hrSTBeKAw=="

def create_sha256_signature(key, message):
    message = bytes(message, 'utf-8')
    return base64.b64encode(hmac.new(key.encode(), message, 
                 digestmod=hashlib.sha256).digest()).decode()

# first request header (possibly the incorrect one)
header = "onoTrUNvGHG6dnaBv+JBJxFod/Vp0m0Dd/B6atdoKpM="

# second request header (possibly the correct one)
header = "onoTrUNvGHG6dnaBv+JBJxFodKVp0m0Dd/B6atdoKpM="
payload = {
    'events':[],
    'firstEventSequence':0,
    'lastEventSequence':0,
    'entropy':
    'YSXCMKAQBJOEMGUZEPFZ'
}
payload = json.dumps(payload, separators=(",", ":")).strip()
signature = create_sha256_signature(XERO_KEY, str(payload))
if hmac.compare_digest(header, signature):
     print(True)
     return 200
else:
     print(False)
     return 401
Shophar answered 13/9, 2019 at 6:41 Comment(0)
R
3

I still could not get this to work even with the OP's answer, which I found a little vague.

The method I found which worked was:

    key = #{your key}
    provided_signature = request.headers.get('X-Xero-Signature')
    hashed = hmac.new(bytes(key, 'utf8'), request.data, hashlib.sha256)
    generated_signature = base64.b64encode(hashed.digest()).decode('utf-8')
    if provided_signature != generated_signature:
        return '', 401
    else:
        return '', 200

found on https://github.com/davidvartanian/xero_api_test/blob/master/webhook_server.py#L34

Responsibility answered 14/4, 2021 at 15:13 Comment(1)
For anyone getting supporting the buffer API required try json.dumps(request.data)Incarnation
S
2

The problem was because when I was receiving the request payload, I was using

# flask request
request.get_json()

this will automatically parse my request data into a json format, hence the reason why the calculated signature never matched

So, what I did was change the way I receive the request payload:

request.get_data()

This will get the raw data.

Shophar answered 17/9, 2019 at 6:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.