APIs & Integrations

Milest
Member

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

Hey Devs! Stuck on a persistent v3 signature mismatch for our public UI Extension that we're looking for some guidance on:

 

`hubspot.fetch()` calls to our local Node.js backend (proxied via `local.json` + Cloudflare tunnel, with `CLIENT_SECRET` injected into `hs project dev`) consistently result in a 403 "Invalid HubSpot signature" from our middleware.

 

Key Findings:

 

  1. `CLIENT_SECRET` is confirmed identical everywhere.
  2. `@hubspot/api-client`'s `Signature.isValid()` returns `false`, even when passed the full URL (from `req.originalUrl` with documented URI decoding), method, empty raw body (for GET), numeric timestamp, and our client secret.
  3. Our manual Node.js `crypto` calculation for the _same base string_ matches standard online HMAC tools, but this "standard" signature does **not** match the signature header sent by the `hs project dev` proxy.


If I understand correctly, this suggests the base string signed by the `hs project dev` proxy differs from our reconstruction (and likely from what `Signature.isValid()` expects in this proxied scenario).

 

Question: Are there any known specific nuances to how `hs project dev` proxy constructs its string-to-sign for v3 (e.g., query param order, specific URL encoding details beyond the documented list) that we might be missing?

Happy to provide more detailed logs/code if anyone has encountered this, and might be willing to provide some direction. Thanks in advance for any insight here!

0 Upvotes
1 Accepted solution
KKauper
Solution
Participant

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

No problem. Here is our code:

 

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

    if (!requestSignature) {
      throw new GeneralError('Did not receive Hubspot signature v3')
    }

    if (!requestTimestamp) {
      throw new GeneralError('Did not receive Hubspot signature v3 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 = <your secret>
    const body = !['GET', 'DELETE'].includes(params.method) ? JSON.stringify(params.originalBody) : ''
    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')
    }

    // Successfully validated

 

I hope it helps.

View solution in original post

5 Replies 5
Milest
Member

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

Thanks so much for sharing your code, @KKauper! It's very helpful to see a working manual implementation.

 

We've also been down the manual calculation path (Node.js/Express) and found, like you, that **bleep** is in the details of that base string, especially the exact uri component.

Our manual Node.js 
crypto calculations actually match standard online HMAC tools for a given base string and our CLIENT_SECRET. However, this "standard" signature still doesn't match the X-HubSpot-Signature-V3 header sent by the hs project dev proxy when using hubspot.fetch() from our UI extension. This suggests the proxy's string-to-sign has a nuance we haven't yet replicated.

 

We're also finding that @hubspot/api-client's Signature.isValid() is similarly failing for us in this proxied dev scenario. Your example reinforces that a precise manual approach can work, so we're continuing to investigate the exact base string formation by the local dev proxy. Appreciate you posting your solution!

 

@DianaGomez - Would love to keep this thread / conversation open to everyone while we continue working through a solution, as we will report back with an update when we do in case it helps anyone else out now or in the future. 

DianaGomez
Community Manager
Community Manager

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

Hi @Milest,

 

Thanks for reaching out to the Community!

 

I would like to invite some members of our community who may offer valuable insights.— hey @EMalueg, @KKauper, @09156, @zach_threadint - Could you share your advice with @Milest?

 

Thanks for taking a look!

Diana


HubSpot’s AI-powered customer agent resolves up to 50% of customer queries instantly, with some customers reaching up to 90% resolution rates.
Learn More.


¿Sabías que la Comunidad está disponible en Español?
¡Participa hoy en conversaciones en el idioma de tu preferencia,cambiando el idioma en tus configuarciones!
0 Upvotes
KKauper
Participant

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

Unfortunately, I can't help here. I do not use this new UI extensions framework, and I don't use `Signature.isValid()` from the Hubspot API client. Instead I use `crypto.timingSafeEqual()` after building the signature. Happy to share my code, however, if it would help.

Milest
Member

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

Thank you, @DianaGomez - I look forward to connecting with more of the community!

@KKauper - I appreciate your quick response and insight. I'm definitely open to trying alternative approaches. Anything that you're able to share would be highly appreciated. Thanks so much again for your help!

KKauper
Solution
Participant

v3 Signature Mismatch - Public UIE (Local Dev + Proxy)

SOLVE

No problem. Here is our code:

 

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

    if (!requestSignature) {
      throw new GeneralError('Did not receive Hubspot signature v3')
    }

    if (!requestTimestamp) {
      throw new GeneralError('Did not receive Hubspot signature v3 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 = <your secret>
    const body = !['GET', 'DELETE'].includes(params.method) ? JSON.stringify(params.originalBody) : ''
    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')
    }

    // Successfully validated

 

I hope it helps.