Elixir v3 Signature Validation

Hello, everybody!
I am having a really difficult time validating requests from HubSpot using the v3 request signature.
Here is the HubSpot documentation:
https://developers.hubspot.com/beta-docs/guides/apps/authentication/validating-requests#validate-the-v3-request-signature

Basically, it says:

1. Create a UTF-8-encoded string that concatenates requestMethod + requestURI + requestBody + timestamp
2. Create an HMAC SHA-256 h_sh of the resulting string using the application secret for the HMAC function.
3. Base64 encode the result of the HMAC function
4. Compare the h_sh value to the “x-hubspot-signature-v3” header. If the values are equal, this request is valid.

Assumptions:
- I have tried using just the path, but I think requestURI should take the form “https://www.mysite.com/path/to/endpoint”.
- “application secret” === dev account > app > “Auth” tab > “Client secret”.

I would like to benefit from the experience of somebody here who has already dealt with this and can show me what I’m doing wrong. Barring that, I invite you to look at my code, spot errors, suggest improvements, etc., and thank you so much in advance!

Here’s my code:
```
uri = “#{scheme}://#{host}#{args[“req_path”]}”

body = params |> Jason.encode!()

# Create a string that concatenates together the following:
# Request Method + Request URI + Request Body + Timestamp
val_str = Enum.join([method, uri, body, timestamp], “”)

# Create a SHA-256 h_sh of the resulting string.
hmac =
:crypto.mac(:hmac, :sha256, secret, val_str)
|> Base.encode64()

# Compare the h_sh value to the signature.
x_hubspot_signature == hmac
```

And here’s what my code produces:
```
X HUBSPOT v3 SIGNATURE: “++IdcWvpfaPQ85JZ3LIYR9YqAm0J2Zy1H5kD6tAgtY0=”

VALIDATION STRING: “POSThttps://my_domain.com/api/v1/hs_send_msg{\“callbackId\”:\“ap-xxxxxxxx-xxxxxxxxxxxxx-9-0\”,\“context\”:{\“source\”:\“WORKFLOWS\”,\“workflowId\”:xxxxxxxxx},\“fields\”:{\“Contact’s Mobile Number\”:\”+12345678910\“,\“Message\”:\“Some test string\”,\“Org\”:\“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - stag\”},\“inputFields\”:{\“Contact’s Mobile Number\”:\”+12345678910\“,\“Message\”:\“Some test string\”,\“Org\”:\“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - stag\”},\“object\”:{\“objectId\”:xxxxxxx,\“objectType\”:\“CONTACT\”},\“origin\”:{\“actionDefinitionId\”:12345678,\“actionDefinitionVersion\”:1,\“actionExecutionIndexIdentifier\”:{\“actionExecutionIndex\”:0,\“enrollmentId\”:1234567891011},\“extensionDefinitionId\”:xxxxxxxx,\“extensionDefinitionVersionId\”:1,\“portalId\”:xxxxxxxx}}1728041677019”

HMAC: “g3wm5qs11huc08xdtp1iy+v97n5dvmx5qqcm83js5y8=”
```

I realize you can’t actually test this, but if you can see what I’m doing wrong, that would be so helpful!

Again, thanks in advance!


@JM927 wrote:

Hello, everybody!
I am having a really difficult time validating requests from HubSpot using the v3 request signature.
Here is the HubSpot documentation:
https://developers.hubspot.com/beta-docs/guides/apps/authentication/validating-requests#validate-the-v3-request-signature

Basically, it says:

1. Create a UTF-8-encoded string that concatenates requestMethod + requestURI + requestBody + timestamp
2. Create an HMAC SHA-256 h_sh of the resulting string using the application secret for the HMAC function.
3. Base64 encode the result of the HMAC function
4. Compare the h_sh value to the “x-hubspot-signature-v3” header. If the values are equal, this request is valid.

Assumptions:
- I have tried using just the path, but I think requestURI should take the form “https://www.mysite.com/path/to/endpoint”.
- “application secret” === dev account > app > “Auth” tab > “Client secret”.

I would like to benefit from the experience of somebody here who has already dealt with this and can show me what I’m doing wrong. Barring that, I invite you to look at my code, spot errors, suggest improvements, etc., and thank you so much in advance!

Here’s my code:
```
uri = “#{scheme}://#{host}#{args[“req_path”]}”

body = params |> Jason.encode!()

# Create a string that concatenates together the following:
# Request Method + Request URI + Request Body + Timestamp
val_str = Enum.join([method, uri, body, timestamp], “”)

# Create a SHA-256 h_sh of the resulting string.
hmac =
:crypto.mac(:hmac, :sha256, secret, val_str)
|> Base.encode64()

# Compare the h_sh value to the signature.
x_hubspot_signature == hmac
```

And here’s what my code produces:
```
X HUBSPOT v3 SIGNATURE: “++IdcWvpfaPQ85JZ3LIYR9YqAm0J2Zy1H5kD6tAgtY0=”

VALIDATION STRING: “POSThttps://my_domain.com/api/v1/hs_send_msg{\“callbackId\”:\“ap-xxxxxxxx-xxxxxxxxxxxxx-9-0\”,\“context\”:{\“source\”:\“WORKFLOWS\”,\“workflowId\”:xxxxxxxxx},\“fields\”:{\“Contact’s Mobile Number\”:\”+12345678910\“,\“Message\”:\“Some test string\”,\“Org\”:\“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - stag\”},\“inputFields\”:{\“Contact’s Mobile Number\”:\”+12345678910\“,\“Message\”:\“Some test string\”,\“Org\”:\“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - stag\”},\“object\”:{\“objectId\”:xxxxxxx,\“objectType\”:\“CONTACT\”},\“origin\”:{\“actionDefinitionId\”:12345678,\“actionDefinitionVersion\”:1,\“actionExecutionIndexIdentifier\”:{\“actionExecutionIndex\”:0,\“enrollmentId\”:1234567891011},\“extensionDefinitionId\”:xxxxxxxx,\“extensionDefinitionVersionId\”:1,\“portalId\”:xxxxxxxx}}1728041677019”

HMAC: “g3wm5qs11huc08xdtp1iy+v97n5dvmx5qqcm83js5y8=”
```

I realize you can’t actually test this, but if you can see what I’m doing wrong, that would be so helpful!

Again, thanks in advance!


Base64-encoded for the HMAC signature calculation if it’s a JSON payload. Ensure that the JSON payload you’re sending matches HubSpot’s request exactly. You should check if there are extra spaces or line breaks in the JSON format that could affect the calculation. Additionally, some systems include headers in the body when making the request, so make sure that only the raw JSON body (and nothing else) is included in the string concatenation.

Thank you for responding, @HMir !
I am base 64 encoding:

‘’’

:crypto.mac(:hmac, :sha256, secret, val_str)

  \|> Base.encode64()

‘’’
Good call-out. I should have included that in the original post.
“Ensure that the JSON payload you’re sending matches HubSpot’s request exactly. You should check if there are extra spaces or line breaks in the JSON format that could affect the calculation.”

Using webhook.site to compare the request JSON to what I’m seeing in my app, I see the raw content looks like this:

{"callbackId":"string","origin":{"portalId":12345678,"actionDefinitionId":12345678,"actionDefinitionVersion":1,"actionExecutionIndexIdentifier":{"enrollmentId":1324507038610,"actionExecutionIndex":0},"extensionDefinitionId":12345678,"extensionDefinitionVersionId":1},"context":{"source":"WORKFLOWS","workflowId":611608228,"actionId":7},"object":{"objectId":51,"objectType":"CONTACT"},"fields":{"Message":"WEBHOOK TEST","Org":"7i988y5 - stag","Contact's Mobile Number":"+12345678910"},"inputFields":{"Message":"WEBHOOK TEST","Org":"7i988y5 - stag","Contact's Mobile Number":"+12345678910"}}

In my original post, it looks like there may be some formatting that I don’t see in my logs, this is what I have:

"{\"callbackId\":\"string\",\"origin\":{\"portalId\":12345678,\"actionDefinitionId\":12345678,\"actionDefinitionVersion\":1,\"actionExecutionIndexIdentifier\":{\"enrollmentId\":1324507038610,\"actionExecutionIndex\":0},\"extensionDefinitionId\":12345678,\"extensionDefinitionVersionId\":1},\"context\":{\"source\":\"WORKFLOWS\",\"workflowId\":611608228,\"actionId\":7},\"object\":{\"objectId\":51,\"objectType\":\"CONTACT\"},\"fields\":{\"Message\":\"WEBHOOK TEST\",\"Org\":\"7i988y5 - stag\",\"Contact's Mobile Number\":\"+12345678910\"},\"inputFields\":{\"Message\":\"WEBHOOK TEST\",\"Org\":\"7i988y5 - stag\",\"Contact's Mobile Number\":\"+12345678910\"}}"

Elixir only uses double quotes (") for strings, so all the double quotes in the JSON body are escaped. I initially worried this was a problem, but I was able to get the same SHA 256 value as the v2 signature example here using the JSON body stringified in this way. So, even though it looks different, it doesn’t seem like that is the problem.

Again, I thank you for your response. I know I must be missing something, I’m just having a very difficult time finding it.