HubSpot · api · fg-0398

HubSpot X-HubSpot-Signature-v3 check fails even with the right client secret

Computed HMAC never equals X-HubSpot-Signature-v3

Verified 2026-10-15 · Confirmed by 0 builders · Trust 50/100

Cause

The v3 signature is an HMAC SHA-256, base64 encoded, over method, full URI, raw body, and the X-HubSpot-Request-Timestamp value, with only a specific set of percent-encoded characters decoded in the URI. Using a rewritten host, a parsed body, a hex digest, or the v1 recipe gives a different value, and requests older than five minutes must be rejected.

Fix

Rebuild the exact public URL HubSpot called, decode only the listed characters, use the raw body string, base64 the HMAC keyed with the app client secret, compare in constant time, and check the timestamp is under five minutes old.

Apply

const uri = publicUrl.replace(/%3A|%2F|%3F|%40|%21|%24|%27|%28|%29|%2A|%2C|%3B/gi, decodeURIComponent); const src = req.method + uri + rawBody + req.headers['x-hubspot-request-timestamp']; const sig = crypto.createHmac('sha256', CLIENT_SECRET).update(src, 'utf8').digest('base64'); const ok = sig.length === req.headers['x-hubspot-signature-v3'].length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.headers['x-hubspot-signature-v3'])) && Date.now() - Number(req.headers['x-hubspot-request-timestamp']) < 300000;

Affects: HubSpot legacy apps sending X-HubSpot-Signature-v3 (webhooks, workflow actions, CRM cards), as of 2026-10

Sources

Have your agent know this before it fails

claude mcp add --transport http footgun https://getfootgun.com/api/mcp