How to verify HelpCrunch webhook signatures

Validate webhook requests using the X-HelpCrunch-Signature header
Written by Konstantine
Updated 23 hours ago

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:

  1. Get the raw request body exactly as it was received.

  2. Generate an HMAC-SHA1 hash using your webhook signing key.

  3. Compare the generated hash with the value from the X-HelpCrunch-Signature header.

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.

Why this matters: if your server parses the JSON and then converts it back into a string, even small changes like key order, spacing, or encoding can make the signature different.

Use constant-time comparison

Do not compare signatures with regular string comparison like == or ===.

Use a constant-time comparison function instead, such as:

  • hash_equals in PHP

  • timingSafeEqual in Node.js

  • compare_digest in 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.

Troubleshooting tip:

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.

Did this answer your question?