In order to help protect our partners and customers, HubSpot signs outgoing requests (such as those for webhooks or CRM cards) so that you can verify that the request did actually come from HubSpot. Following the latest security best-practices, we are adding two new headers to outgoing HubSpot requests to OAuth Apps - X-HubSpot-Signature-v3 and X-HubSpot-Request-Timestamp. Prior versions of the X-HubSpot-Signature header will continue to be included for backward compatibility. OAuth Apps can use the request signature to verify whether received requests are actually from HubSpot.
What’s happening
We are adding two new headers to outgoing requests: X-HubSpot-Signature-v3, X-HubSpot-Request-Timestamp.
To verify the signature, developers will need to perform the following steps:
Reject the request if the timestamp is older than 5 minutes.
Create a utf-8 encoded string that concatenates together the following: requestMethod + requestUri + requestBody + timestamp. The timestamp is provided by the new X-HubSpot-Request-Timestamp header.
Create an HMAC SHA-256 hash of the resulting string using the application secret as the secret for the HMAC SHA-256 function.
Base64 encode the result of the HMAC function.
Compare the hash value to the signature. If they're equal then this request has been verified as originating from Hubspot. We recommend using constant-time string comparison to guard against timing attacks.
These new headers are available now.
If you have any questions or comments, please join the discussion here.
Join us on March 27th at 12 PM for the Digital Essentials Lab, an interactive session designed to redefine your digital strategy!
Engage with expert Jourdan Guyton to gain actionable insights, participate in live Q&A, and learn strategies to boost your business success. Don't miss this opportunity to connect and grow—reserve your spot today!
I'm experiencing issues validating the request. Here's my code, what am I getting wrong here? Please note the _fullWebHookUrl is the entire path (b692-98-231-78-177.ngrok.io/api/v1/HubSpotWebhooks/PostNotification)
Thanks for the help, I'm pretty well frustrated at this point.
private bool ValidateRequest(HttpRequest httpRequest, dynamic requestBody) { httpRequest.Headers.TryGetValue("X-Hubspot-Request-Timestamp", out var timeStamp); httpRequest.Headers.TryGetValue("X-Hubspot-Signature-V3", out var v3);
[HttpPost] public async Task<IActionResult> Webhook() { var res = await ValidateRequest(HttpContext); return Ok(res); } private async Task<bool> ValidateRequest(HttpContext httpContext) { httpContext.Request.Headers.TryGetValue("X-Hubspot-Request-Timestamp", out var timeStamp); httpContext.Request.Headers.TryGetValue("X-Hubspot-Signature-V3", out var v3); var webHookUrl = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}{httpContext.Request.Path}"; var fullWebhookUrl = new Uri(webHookUrl).AbsoluteUri; HMACSHA256 hashObject = new(Encoding.UTF8.GetBytes(_appSecret)); var payload = ""; using (var reader = new StreamReader(httpContext.Request.Body, leaveOpen: true)) { payload = await reader.ReadToEndAsync(); } var data = string.Concat(httpContext.Request.Method, fullWebhookUrl, payload, timeStamp); var utf = Encoding.UTF8.GetBytes(data); var signatureV3 = hashObject.ComputeHash(utf); var encodedSignature = Convert.ToBase64String(signatureV3); var validRequest = v3.Equals(encodedSignature); return validRequest; }
Sorry, that was the first thing that stood out to me. I'm not familiar with .NET but your logic looks correct to me. I have it working in Node.js which I can clean up and post as an example here if that'd be helpful.
The string I'm concatening and hashing for a CRM card looks like:
Keep in mind, yours will look a little different depending on what's in the request body for your webhook POST and if any query string parameters are being passed. I have no request body for a CRM card GET request:
portalId=XXXXXXXX<the request body would go here if there was one>1648664076585
Hi @zwolfson, I don't find any way to specify the Signature Version(like v3), and now I find the webhook from hubspot is just v1, do I need to implement the three version verify method according to the version in
I am working on X-Hubspot-Signature-V3 validation using below c# code but i am not getting correct hash value. can anyone suggest working c# code sample?
HttpContext.Request.Headers.TryGetValue("X-Hubspot-Request-Timestamp", out StringValues timeStamp ); HttpContext.Request.Headers.TryGetValue("X-Hubspot-Signature-V3", out StringValues v3); var validRequest = false; using (var stream = new StreamReader(HttpContext.Request.Body)) { var body = await stream.ReadToEndAsync(); var method = HttpContext.Request.Method; var requestUri = $"{Request.Scheme}://{Request.Host.Value}/"; HMACSHA256 hashObject = new HMACSHA256(Encoding.UTF8.GetBytes("screat")); var data = string.Concat(method, requestUri, body, timeStamp); byte[] utf = Encoding.UTF8.GetBytes(data); var signatureV3 = hashObject.ComputeHash(Encoding.UTF8.GetBytes(data)); var encodedSignature = Convert.ToBase64String(signatureV3); if (v3.Equals(encodedSignature)) { validRequest = true; }
I'm still trying to wrap my head around on how to concatinate the string correctly. I wish there's an example in the doc for us to confirm that our code is correct.
By any chance, does anyone has a php working sample? I kinda have this, but I can never generate the same signature as the header. Not sure what i'm doing wrong. Thought V3 is suppose to be more securied, but so hard to get it right.
// Create a utf-8 encoded string that concatenates together the following: requestMethod + requestUri + requestBody + timestamp. The timestamp is provided by the new X-HubSpot-Request-Timestamp header.
$request_method = $_SERVER['REQUEST_METHOD'];
$request_uri = $_SERVER['SCRIPT_URI'];
$request_body = file_get_contents('php://input');
$request_timestamp = $_SERVER['HTTP_X_HUBSPOT_REQUEST_TIMESTAMP'];
$string = utf8_encode("{$request_method}{$request_uri}{$request_body}{$request_timestamp}");
// Create an HMAC SHA-256 hash of the resulting string using the application secret as the secret for the HMAC SHA-256 function.
$hash = hash_hmac('sha256', $string, HUBSPOT_APP_SECRET);
// Base64 encode the result of the HMAC function.
$encoded = base64_encode($hash);
// Compare the hash value to the signature. If they're equal then this request has been verified as originating from Hubspot. We recommend using constant-time string comparison to guard against timing attacks.
var_dump(hash_equals($_SERVER['HTTP_X_HUBSPOT_SIGNATURE_V3'], $encoded));
Hi @AChen4 & @RSheladiya .... we're you able to eventually get past the probem and successfully create a working payload signature? We're considering a move to v3, and just want to ensure that the path is worthwhile. Do you have working pseudo-solutions to share? Thanks.
@zwolfson regardless of not selecting into the v3 signature approval depicted in your post, our unmistakable approval out of nowhere broke at 10:15am PT today. Your articulation around guaranteeing in reverse similarity doesn't appear to be precise as far as we can tell.
This was working when payload is small size of json, Fails on Form submission webhook. The form submission webhook payload its too large. Also it's very strange to debug this issue.