When HelpCrunch sends a webhook request, it includes the X-HelpCrunch-Signature header. This header is used to verify that the request truly came from HelpCrunch and that its payload was not modified in transit.
If your app rejects incoming webhook requests because of an invalid signature, the most common reason is that the signature is being checked against a parsed or modified request body instead of the original raw payload.
How webhook signature verification works
To verify a HelpCrunch webhook request on your side:
-
Get the raw request body exactly as it was received.
-
Generate an HMAC-SHA1 hash using your webhook signing key.
-
Compare the generated hash with the value from the
X-HelpCrunch-Signatureheader.
The signature is always sent with HelpCrunch webhook requests, so if the validation fails, the problem is usually in how the payload is processed before verification.
Important requirements
Use the raw request body
You must validate the signature against the raw body of the request, before any JSON parsing or re-serialization.
Use constant-time comparison
Use a constant-time comparison function instead, such as:
-
hash_equalsin PHP -
timingSafeEqualin Node.js -
compare_digestin Python
This helps protect your endpoint from timing attacks.
Signature format
HelpCrunch uses:
-
Algorithm: HMAC-SHA1
-
Output format: hexadecimal string
-
Length: 40 characters
PHP example
function verifyWebhookSignature(string $rawBody, string $signingKey, string $receivedSignature): bool
{
$expectedSignature = hash_hmac('sha1', $rawBody, $signingKey);
return hash_equals($expectedSignature, $receivedSignature);
}
// Usage
$rawBody = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_HELPCRUNCH_SIGNATURE'] ?? '';
$signingKey = 'your-webhook-signing-key';
if (!verifyWebhookSignature($rawBody, $signingKey, $receivedSignature)) {
http_response_code(401);
exit('Invalid signature');
}
Node.js example
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signingKey, receivedSignature) {
const expectedSignature = crypto
.createHmac('sha1', signingKey)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
// Usage (Express)
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const receivedSignature = req.headers['x-helpcrunch-signature'];
if (!verifyWebhookSignature(req.body, process.env.SIGNING_KEY, receivedSignature)) {
return res.status(401).send('Invalid signature');
}
// process webhook...
});
Python example
import hmac
import hashlib
def verify_webhook_signature(raw_body: bytes, signing_key: str, received_signature: str) -> bool:
expected_signature = hmac.new(
signing_key.encode(),
raw_body,
hashlib.sha1
).hexdigest()
return hmac.compare_digest(expected_signature, received_signature)
# Usage (Flask)
from flask import Flask, request
@app.route('/webhook', methods=['POST'])
def webhook():
received_signature = request.headers.get('X-HelpCrunch-Signature', '')
if not verify_webhook_signature(request.get_data(), SIGNING_KEY, received_signature):
return 'Invalid signature', 401
Common reasons why signature verification fails
If the signature does not match, check the following:
1. The request body is not raw
This is the most common issue. Make sure you hash the original request body, not a parsed JSON object or a re-stringified version of it.
2. The wrong signing key is used
Double-check that your app is using the exact webhook signing key configured for that webhook.
3. The signature is compared incorrectly
Use a constant-time comparison function instead of a standard equality check.
4. The request body is modified by middleware
Some frameworks automatically parse or transform incoming JSON before your verification code runs. In this case, configure the endpoint to access the raw body first.
If your webhook works in Postman but fails with real HelpCrunch requests, the issue is often not the header itself, but the way your server handles the payload before calculating the hash.
In other words, manually sending a request with the same header value in Postman does not guarantee that your production signature check is implemented correctly.