Froovo Sign
Back to Blog
Sign InGet Started
Security

Understanding HMAC Webhook Signatures

Learn how HMAC-SHA256 signatures protect your webhook endpoints and how to verify them in your application.

F

Froovo Team

Security

May 15, 20255 min read

Webhooks are HTTP callbacks — your server receives a POST request whenever an event happens. But without verification, anyone can POST fake events to your endpoint. HMAC signatures solve this.

What are webhook signatures?

A webhook signature is a cryptographic hash included in the request headers. The sending server generates it using a shared secret and the raw request body. Your server re-computes the hash and compares — if they match, the request is authentic.

Why they matter

  • Prevents spoofed events — an attacker without your secret can't generate a valid signature
  • Protects against replay attacks when combined with timestamp validation
  • Required for SOC 2 and similar compliance frameworks
  • Gives you confidence to take irreversible actions (charge a card, grant access) based on webhook data

How Froovo Sign implements HMAC-SHA256

Every Froovo Sign webhook request includes an X-Froovo-Signature header in the format sha256=<hex_digest>. The digest is computed over the raw JSON body using your webhook secret as the key.

Verifying signatures in Node.js

typescript
import crypto from "crypto";

function verifyFroovoWebhook(
  rawBody: string,
  signature: string,
  secret: string
): boolean {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Verifying signatures in Python

python
import hmac
import hashlib

def verify_froovo_webhook(raw_body: str, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        raw_body.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    # Use hmac.compare_digest to prevent timing attacks
    return hmac.compare_digest(signature, expected)

Important: use the raw body

Always compute the HMAC over the raw request body bytes — not a re-serialized JSON object. JSON serializers may reorder keys or alter whitespace, producing a different hash even for identical payloads. In Express, use express.raw() middleware. In Next.js App Router, use request.text() before parsing.

Back to Blog

Related articles

TutorialHow to Add E-Signatures to Your SaaS in Under an Hour
GuideThe Complete Guide to Digital Signature Audit Trails