Difficulty validating hash posted to webook

SOLVE
Occasional Contributor

LS,

 

For our hubspot integeration we make use of webhooks so see when properties change, for the authentication of requests being made from hubspot to our system we are going to use the `X-Hubspot-Signature` headers as explained in the documentation:

https://developers.hubspot.com/docs/faq/validating-requests-from-hubspot

 

We use a python system and I was able to mimic hashing behaviour as used in the example. Because of this I wanted to start testing using `test subscriptions` in hubspot GUI.

 

What I did is setup a https://putsreq.com bucket to catch my test request and test if the my hashing method of the request results in the same hash als was in the header. Sadly this was not the case.

 

The core of my question is exactly how does hubspot hash the request before sending it? constructing it exactly like in the documentation does not yield the correct result, here I took the correct secret key, the test url and the request body as formated when using `Copy JSON` button.

 

Does anybody know what I might be doing wrong?

 

Below you can find a python code snippet I used to test this. Here the example from the documentation does work, but my test from hubspot itself does not.

from hashlib import sha256

def main():
    # example request from documentation:
    # https://developers.hubspot.com/docs/faq/validating-requests-from-hubspot
    secret = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    url = "https://www.example.com/webhook_uri"
    body = '{"example_field":"example_value"}'
    xsig = "9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900"
    validate(secret, url, body, xsig)


    # Test request made by hubspot from https://app.hubspot.com/developer/5153176/application/189009/webhooks
    # The app is only used to debug this exact issue
    secret = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" # need to be replaced with secret from app 189009
    # To see requests send here check https://putsreq.com/UKQCqLn1pKDbA1IBpt8S/inspect
    url = "https://putsreq.com/UKQCqLn1pKDbA1IBpt8S"
    # Direct paste from 'Copy JSON' option in the hubspot webhook overview.
    # In our system this data is retreived from this get_data using the `as_text` flag.
    # https://tedboy.github.io/flask/generated/generated/flask.Request.get_data.html
    body = """[
  {
    "eventId": 1,
    "subscriptionId": 104266,
    "portalId": 5153176,
    "occurredAt": 1550566240770,
    "subscriptionType": "contact.propertyChange",
    "attemptNumber": 0,
    "objectId": 123,
    "changeSource": "CRM",
    "propertyName": "email",
    "propertyValue": "sample-value",
    "appId": 189009
  }
]"""
    # Signature taken from the X-Hubspot-Signature header in the request send to putsreq
    xsig = "237092981e91b0b56714f7c72d42a59736fd99b45f7b22194e5bd84726837136"
    validate(secret, url, body, xsig)


def validate(secret: str, url: str, body: str, xsig: str) -> bool:
    """
    Compare the hash of the request with the given X-Hubspot-Signature
    """
    # build the source of the hash and encode it into bytes so sha256 can hash it.
    source_bytes = f"{secret}POST{url}{body}".encode("UTF-8")
    local_hash = sha256(source_bytes).hexdigest()
    eq = xsig == local_hash

    print(
        f"Local hash:\t{local_hash}\n"
        f"Expected hash:\t{xsig}\n"
        f"Equal:\t\t{eq}\n"
        f"String before hashing:\n{source_bytes}\n"
    )
    return eq


if __name__ == "__main__":
    main()
Reply
0 Upvotes
1 Accepted solution

Accepted Solutions
Highlighted
HubSpot Moderator

Hi @Gijs_vs , happy to help here. Very weird and confusing quirk with HubSpot, but at the top of the article you linked to me, there's a note that says:

 

Note: Requests sent by the Webhooks API use a different validation method. Please see the instructions in the Webhooks Overview for more details

 

For Webhooks you should validate by concatenating just the app secret and the unparsed request body - more info here: https://developers.hubspot.com/docs/methods/webhooks/webhooks-overview#security

3 Replies 3
Highlighted
HubSpot Moderator

Hi @Gijs_vs , happy to help here. Very weird and confusing quirk with HubSpot, but at the top of the article you linked to me, there's a note that says:

 

Note: Requests sent by the Webhooks API use a different validation method. Please see the instructions in the Webhooks Overview for more details

 

For Webhooks you should validate by concatenating just the app secret and the unparsed request body - more info here: https://developers.hubspot.com/docs/methods/webhooks/webhooks-overview#security

Highlighted
New Contributor

This is pretty confusing because this page about webhooks in workflows:

https://knowledge.hubspot.com/articles/kcs_article/workflows/how-do-i-use-webhooks-with-hubspot-work...

 

Links to this page:

 

https://developers.hubspot.com/docs/methods/crm-extensions/crm-extensions-overview#request-signature...

 

Which then says.. See this page for more details and examples: https://developers.hubspot.com/docs/faq/validating-requests-from-hubspot:

 

Where do you find the "app secret" is this the "client secret"? Also, is there a way to know what the  unparsed request body will be? Basically, in the workflow it sends the entire json object for a client, how would our app be able to know what that is to create the hash to compare against?

 

Thanks in advance for your help

Reply
0 Upvotes
Highlighted
HubSpot Moderator

Hi @bashr , your client ID and secret are found in your HubSpot App in your Developer Account. The payload that's sent through a webhook is the entire contact, company or deal object in JSON format. Your app would need to calculate the hash as the payload is sent to it. In NodeJS, it's something like req.body. One of the headers of that request will be the hash calculated on HubSpot's end, and that's what you'll compare against. 

Reply
0 Upvotes