I’m working on integrating HubSpot webhooks with my PHP application and having trouble with signature verification. I need to validate incoming webhook requests using the v3 signature method but my validation keeps failing.
The validation always returns false even though I’m following the documentation. The generated signature doesn’t match what HubSpot sends in the header. What could be causing this mismatch? Any ideas on what I might be doing incorrectly in my signature generation process?
Had the same HubSpot v3 signature issues - it’s usually the request URI handling that’s the problem. Your code looks right, but there’s a catch. $_SERVER['REQUEST_URI'] grabs the full path with query params, but URL encoding trips people up. HubSpot calculates signatures using the exact URI it sends, encoded characters and all. Log your computed signature and source string to spot the difference. For me, the server was auto-decoding URL params while HubSpot used the original encoded version. Also check if your server’s messing with the raw payload. Some PHP configs or middleware will change the request body before file_get_contents('php://input') gets it. I had to kill a few Apache modules that were causing problems. Last thing - double-check you’re using the client secret from the right HubSpot app, not the API key. Signature verification needs the client secret specifically.
The Problem: You’re experiencing signature verification failures when integrating HubSpot webhooks with your PHP application using the v3 signature method. Your current implementation generates a signature that doesn’t match the one sent by HubSpot, resulting in validation always returning false.
Understanding the “Why” (The Root Cause):
The discrepancy between your generated signature and HubSpot’s signature likely stems from subtle differences in how the source string is constructed and/or the handling of the request data. Several factors can contribute to this:
Inconsistent REQUEST_URI: The $_SERVER['REQUEST_URI'] variable’s behavior can vary depending on your web server (Apache, Nginx) and any intervening CDNs or load balancers (like CloudFlare). HubSpot calculates the signature using the exact URI it receives, including query parameters and URL encoding. Any discrepancies in URL encoding or the presence/absence of query parameters will lead to signature mismatches.
Payload Manipulation: Your web server’s configuration (PHP settings, Apache modules, security middleware) might modify the request body ($payload_data) before it reaches your script. Even a single byte difference in the payload will invalidate the signature. Modules like mod_deflate (if you’re using Apache) that handle gzip compression can alter the raw data.
Timestamp Discrepancies: The timestamp ($received_timestamp) must be within a 5-minute window of the server’s current time. Incorrect server timezone settings, or delays in processing the request, can cause this check to fail.
Character Encoding: Ensure there’s no automatic character set conversion or content filtering applied by your server or hosting provider to the payload data. This can change the byte representation and lead to signature mismatches. The content length (Content-Length header) from HubSpot should match the length of your received payload.
Step-by-Step Guide:
Verify the Source String: Log the entire source string you’re using to generate the signature ($source_string) and the HubSpot signature ($hubspot_signature) received in the header. Compare them character by character. Pay close attention to URL encoding in the URI and any differences in the payload data’s byte representation.
Bypass Middleware (Temporarily): To isolate payload manipulation, temporarily disable any potentially interfering Apache modules (like mod_deflate) or PHP middleware. If the signature verification starts working, you’ve found the culprit.
Hardcode the Endpoint URL: Replace $_SERVER['REQUEST_URI'] with a hardcoded, URL-encoded version of the exact URI HubSpot uses for the webhook request. This eliminates any server-side URI manipulation discrepancies.
Precise Timestamp Comparison: Log the difference between the HubSpot timestamp and your server’s timestamp. Ensure your server’s timezone is correctly configured. If the difference is greater than 5 minutes, investigate potential delays in request processing.
Double-Check the App Secret: Confirm you are using the correct client secret from your HubSpot app (not the API key). The client secret is specifically used for signature verification.
Common Pitfalls & What to Check Next:
Proxy Servers/Load Balancers: If your server is behind a proxy or load balancer, the REQUEST_URI may be rewritten. Check your proxy/load balancer configurations.
Byte-Exact Payload: The payload must be byte-for-byte identical. Use a tool like hexdump or od to examine the raw bytes of your received payload and compare it to HubSpot’s Content-Length header.
Whitespace: Ensure there is no extra whitespace in any part of your source string.
Debugging Tools: Employ logging statements to inspect each part of the signature generation process (HTTP method, URI, payload, timestamp) to pinpoint the exact point of divergence.
Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!
Your issue’s probably with the source string construction or timestamp validation. HubSpot v3 signatures don’t mess around with format.
Common gotchas:
REQUEST_URI needs query parameters if they exist
Timestamp must be within 5 minutes of current time
App secret has to match HubSpot exactly
Some servers add random headers that break the payload
Honestly though, debugging webhook signatures is a nightmare. You’ll waste hours on weird edge cases.
I just route everything through Latenode now. It handles HubSpot webhook verification automatically, then sends clean data to my PHP app via simple HTTP. No more signature hell.
Create a webhook endpoint in Latenode, connect to HubSpot, add your processing logic, send to PHP. Takes 10 minutes vs hours debugging crypto functions.
Bonus: you get retry logic, logging, and can add other integrations later without touching your main code.
Been fighting HubSpot webhook signatures for months and hit this same issue. Character encoding was my problem. When you grab the payload with file_get_contents('php://input'), check if your server’s doing any automatic charset conversion or content filtering. Some hosts run security middleware that tweaks POST data just enough to break signatures completely. Log your payload’s exact byte length and compare it to HubSpot’s Content-Length header. The endpoint URL format also got me - HubSpot wants the full path exactly as they called it, trailing slashes and encoded characters included. If you’re behind a proxy or load balancer, REQUEST_URI might not match what HubSpot actually hit. Try hardcoding the endpoint path to rule that out. And double-check your app secret isn’t getting trimmed when you store it in config.
check your timestamp validation - that’s proably the culprit. hubspot v3 auto-fails signatures if the timestamp is off by more than 5 min. also verify your server timezone is correct. I had mine set wrong and sigs kept failing even tho everything else looked good. log the timestamp diff between hubspot and yer server first, then worry about debuggin the sig generation.
Had this exact issue with HubSpot webhooks last year. The signature mismatch is usually PHP’s input stream handling screwing things up. Call file_get_contents('php://input') right at the start before doing anything else. Frameworks or error handlers can partially consume the stream, so you end up with incomplete payload data. Check your web server config too - make sure it’s not doing automatic decompression. I had mod_deflate messing with gzipped HubSpot payloads, which completely broke signature validation. Also watch out for hosting providers that strip or modify headers. The timestamp header has to arrive exactly as HubSpot sent it. Debug by logging the raw payload length and computed source string to see where things go wrong.