hubspot-api

Occassional Hubspot webhook signature mismatch


I have a service which listens to Hubspot webhook requests (POSTs), and authenticates each request using Hubspot's v3 signature. The code is this (typescript):

const requestSignature = params?.headers?.['x-hubspot-signature-v3']
const requestTimestamp = params?.headers?.['x-hubspot-request-timestamp']

// Validate timestamp
const MAX_ALLOWED_TIMESTAMP = 300000 // 5 minutes in milliseconds
const currentTime = Date.now()
if (currentTime - requestTimestamp > MAX_ALLOWED_TIMESTAMP) {
  throw new GeneralError('Hubspot signature v3 timestamp is invalid')
}

// Calculate signature
const clientSecret = <...snip: get secret from secrets store...>
const body = JSON.stringify(params.body)
const source = params.method + params.uri + body + requestTimestamp
const signature = crypto.createHmac('sha256', clientSecret).update(source).digest('base64')

// Validate signature
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(requestSignature))) {
  throw new NotAuthenticated('Hubspot signature v3 mismatch')
}

This works most of the time, but occasionally the signatures don't match. It's not because of any missing data, or incorrect data on our side -- I have verified that the correct headers exist, etc. And it's not the call to crypto.timingSafeEqual -- I have verified that the two signatures do not match.

Has anyone else experienced this before? What could cause just some of the requests from Hubspot to fail signature validation?


Solution

  • @CBroe, in his comment above, provided a hint to the answer, but the problem actually came before JSON.stringify(): Our app uses global hooks which transforms the request payload in some cases. In this case, one of the string values in the payload JSON had a trailing space, and one of our hooks trims all input data. Executing the above signature validator with the original un-processed request payload solved the problem.