The Shack Security How to Generate and Use HMAC Signatures

How to Generate and Use HMAC Signatures for API Security

Back to All Posts

When a webhook fires or an API call arrives, how does the server know the request is legitimate — that it actually came from who it claims to come from, and that nobody tampered with the payload in transit? Bearer tokens prove identity, but they don't prove integrity. HMAC signatures prove both.

Generate an HMAC signature immediately with the DevToolShack HMAC Generator — choose your algorithm, enter your key and message, and get the signature in hex or Base64. Free, browser-based, nothing stored.

What Is HMAC?

HMAC (Hash-based Message Authentication Code) combines a cryptographic hash function with a secret key to produce a signature. The formula is:

HMAC(key, message) = Hash(key ⊕ opad || Hash(key ⊕ ipad || message))

In practice you don't need to implement this — every language has HMAC built in. What matters is what it achieves:

  • Authentication — only someone with the secret key can produce the correct signature
  • Integrity — any modification to the message produces a completely different signature
  • Non-repudiation — the signature proves the message came from a key holder

How It Works in Practice: Webhook Signatures

The most common developer encounter with HMAC is webhook signature verification. Here's how Stripe, GitHub, Shopify, and most other webhook providers implement it:

  1. When you configure a webhook, the provider gives you a secret key
  2. When they send a webhook, they compute HMAC-SHA256(secret, request_body)
  3. They include the signature in a request header (e.g., X-Signature: sha256=abc123...)
  4. Your server receives the request, recomputes the signature using the same secret and body
  5. If the signatures match, the webhook is authentic and unmodified
Use a timing-safe comparison. When verifying HMAC signatures, never use === or == to compare them. Standard string comparison short-circuits on the first mismatch, creating a timing attack vulnerability. Use your language's constant-time comparison function instead.

Implementing HMAC Signature Verification

// Node.js — verifying a webhook signature
import crypto from 'crypto';

function verifyWebhookSignature(payload, signature, secret) {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  const sigBuffer = Buffer.from(signature, 'hex');
  const computedBuffer = Buffer.from(computed, 'hex');

  if (sigBuffer.length !== computedBuffer.length) return false;
  return crypto.timingSafeEqual(sigBuffer, computedBuffer);
}

// Express middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'].replace('sha256=', '');
  const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  // Process the webhook...
});
# Python — verifying a webhook signature
import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    # hmac.compare_digest is timing-safe
    return hmac.compare_digest(computed, signature)

Signing Outgoing API Requests

HMAC is also used when you're the one calling an API that requires request signing (AWS Signature Version 4, for example). The pattern is:

  1. Build the canonical request string — typically method + URL + headers + body hash
  2. Sign it with your secret key using HMAC-SHA256
  3. Include the signature in the Authorization header
// Signing an outgoing request
import crypto from 'crypto';

function signRequest(method, url, body, secretKey) {
  const timestamp = Date.now().toString();
  const message = `${method}\n${url}\n${timestamp}\n${body}`;

  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(message)
    .digest('hex');

  return {
    'X-Timestamp': timestamp,
    'X-Signature': signature
  };
}

// Add the returned headers to your fetch/axios request

Choosing the Right Hash Algorithm

AlgorithmOutput SizeUse Today?
HMAC-MD5128 bitsLegacy only — MD5 is weak
HMAC-SHA1160 bitsAvoid for new systems
HMAC-SHA256256 bitsYes — current standard
HMAC-SHA512512 bitsYes — higher security margin

HMAC-SHA256 is the right default for new implementations — it's what GitHub, Stripe, Shopify, and most modern APIs use. Note that HMAC-MD5 and HMAC-SHA1 are much stronger than plain MD5/SHA1 (the HMAC construction adds significant security), but for new systems there's no reason not to use SHA256.

HMAC vs JWT vs API Keys

MethodProves IdentityProves IntegrityStateless
API Key in headerYesNoYes
JWT (HS256)YesYes (token only)Yes
HMAC request signingYesYes (full payload)Yes
Debug your signatures: When integrating with a webhook provider and signatures aren't matching, the HMAC Generator lets you manually reproduce the signature computation — paste the exact payload and your secret key to verify your implementation is producing the expected output. Much faster than adding debug logging and waiting for another webhook delivery.