HMAC Guide: SHA-256 Signatures, Webhook Verification & API Authentication

By Suvom Das March 27, 2026 20 min read

1. What Is HMAC?

HMAC stands for Hash-based Message Authentication Code. It is a specific type of message authentication code (MAC) that uses a cryptographic hash function (such as SHA-256 or SHA-512) combined with a secret key. Defined in RFC 2104 (published in 1997) and further standardized in FIPS PUB 198-1, HMAC has become one of the most widely used authentication mechanisms in modern computing.

The fundamental purpose of HMAC is to provide two guarantees about a message: data integrity (the message has not been altered since the HMAC was computed) and data authentication (the HMAC was generated by someone who possesses the secret key). These two properties together make HMAC essential for securing communication channels where both parties share a secret key.

You encounter HMAC-based authentication every day, even if you do not realize it. When your application receives a webhook from GitHub, Stripe, or Slack, the service signs the payload with HMAC-SHA256 using a shared secret. When you make an API call to AWS, the request is signed with HMAC-SHA256 as part of Signature Version 4. When your browser sends a cookie, the server may use HMAC to verify that the cookie value has not been tampered with. HMAC is everywhere in web infrastructure.

A Simple Analogy

Think of HMAC as a tamper-evident seal on a package, combined with a shared secret handshake. Anyone can see the package (the message), but only someone who knows the secret can create a valid seal. When the recipient receives the package, they use the same secret to create their own seal. If the seals match, the package is authentic and unmodified. If they differ, either the package was tampered with or the sender did not know the secret.

2. How HMAC Works Internally

Understanding the internal construction of HMAC helps explain why it is more secure than simply concatenating a key with a message and hashing the result. The HMAC algorithm, as defined in RFC 2104, uses two passes of hashing with specially constructed inner and outer keys.

The HMAC Formula

The mathematical definition of HMAC is:

HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))

Where:

Step-by-Step Process

The HMAC computation proceeds in several steps:

  1. Key preparation: If the secret key is longer than the hash function's block size (64 bytes for SHA-256, 128 bytes for SHA-512), hash it first to reduce it to the hash output size. If shorter, pad it with zeros to the block size. This gives us K'.
  2. Inner hash: XOR K' with the ipad constant (0x36 repeated). Concatenate this with the message. Hash the result with the hash function. This produces the inner hash.
  3. Outer hash: XOR K' with the opad constant (0x5c repeated). Concatenate this with the inner hash output. Hash the result again. This produces the final HMAC value.

The two-pass construction is what makes HMAC resistant to length extension attacks. Even if an attacker knows the hash of a message, they cannot extend it with additional data and compute a valid HMAC without knowing the secret key.

Why Not Just Hash(key + message)?

A common question is why HMAC uses this complex two-pass construction instead of simply computing SHA256(key + message). The answer is that hash functions based on the Merkle-Damgard construction (which includes SHA-256 and SHA-512) are vulnerable to length extension attacks. An attacker who knows SHA256(key + message) can compute SHA256(key + message + padding + extension) without knowing the key. The HMAC construction prevents this attack entirely.

3. HMAC vs Plain Hash

Understanding the distinction between HMAC and plain hashing is critical for choosing the right tool in security applications. While both produce fixed-size outputs from variable-size inputs, they serve fundamentally different purposes.

Plain Hash (SHA-256)

HMAC (HMAC-SHA256)

When to Use Each

Use a plain hash when you need to verify data integrity without authentication: file checksum verification, content-addressable storage, deduplication, and cache key generation. Use HMAC when you need to verify both integrity and authenticity: API request signing, webhook verification, session token generation, and any scenario where you need to prove that a message was created by a trusted party.

4. Choosing an HMAC Algorithm

HMAC can be used with any cryptographic hash function, but the three most common choices are SHA-256, SHA-384, and SHA-512. The algorithm names are typically written as HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512.

HMAC-SHA256

HMAC-SHA256 is the most widely used and recommended algorithm. It produces a 256-bit (32-byte) signature, typically represented as a 64-character hex string or a 44-character base64 string. It is the standard for AWS Signature V4, GitHub webhook signatures, Stripe webhook signatures, Slack request verification, and most modern API authentication schemes. Unless you have a specific reason to use a different algorithm, HMAC-SHA256 is the right default choice.

HMAC-SHA384

HMAC-SHA384 produces a 384-bit (48-byte) signature. It is a truncated version of SHA-512 internally and is sometimes used in TLS cipher suites (e.g., TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). It is less common in API authentication but provides a middle ground between SHA-256 and SHA-512.

HMAC-SHA512

HMAC-SHA512 produces a 512-bit (64-byte) signature. It uses 64-bit arithmetic internally, which makes it faster than SHA-256 on 64-bit processors. It is a good choice when you need a longer signature, are working in environments where 64-bit performance matters, or when the specification you are implementing requires SHA-512 (some government and financial protocols do).

Deprecated: HMAC-SHA1 and HMAC-MD5

HMAC-SHA1 and HMAC-MD5 are still technically secure as HMAC constructions (the HMAC construction compensates for the weaknesses in SHA-1 and MD5), but they are deprecated by modern standards. New implementations should use HMAC-SHA256 or stronger. Some legacy systems still use HMAC-SHA1 (e.g., older OAuth implementations), but migration to SHA-256 is strongly recommended.

5. Webhook Signature Verification

One of the most common real-world applications of HMAC is webhook signature verification. When a service like GitHub, Stripe, or Slack sends a webhook to your server, it signs the request body with a shared secret using HMAC. Your server must verify this signature to ensure the webhook is authentic and has not been tampered with.

How Webhook Signing Works

  1. You configure a webhook URL and a shared secret in the service's dashboard.
  2. When an event occurs, the service computes HMAC-SHA256(secret, request_body).
  3. The service includes the HMAC signature in a request header (e.g., X-Hub-Signature-256).
  4. Your server receives the webhook, extracts the signature from the header, computes its own HMAC using the same secret, and compares the two values.
  5. If they match, the webhook is authentic. If not, reject it.

GitHub Webhook Example

GitHub sends webhook signatures in the X-Hub-Signature-256 header with the format sha256=<hex-hmac>. Here is how to verify it in Node.js:

const crypto = require('crypto');

function verifyGitHubWebhook(payload, signature, secret) {
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(payload, 'utf-8');
    const computed = 'sha256=' + hmac.digest('hex');

    // Use constant-time comparison to prevent timing attacks
    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(signature)
    );
}

// In your webhook handler:
app.post('/webhook', (req, res) => {
    const signature = req.headers['x-hub-signature-256'];
    const payload = req.body; // raw body string

    if (!verifyGitHubWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
        return res.status(401).send('Invalid signature');
    }

    // Process the webhook...
    res.status(200).send('OK');
});

Stripe Webhook Example

Stripe uses a more complex signing scheme with timestamps to prevent replay attacks. The signature header Stripe-Signature contains both a timestamp and the HMAC value.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/stripe-webhook', (req, res) => {
    const sig = req.headers['stripe-signature'];
    const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

    try {
        const event = stripe.webhooks.constructEvent(
            req.body, sig, endpointSecret
        );
        // Process event...
        res.json({ received: true });
    } catch (err) {
        res.status(400).send(`Webhook Error: ${err.message}`);
    }
});

6. API Request Signing

Beyond webhooks, HMAC is widely used for signing outbound API requests. The most prominent example is AWS Signature Version 4, which uses HMAC-SHA256 to authenticate every API request to AWS services.

AWS Signature Version 4

AWS Sig V4 is a multi-step HMAC-based signing process. At its core, it computes a chain of HMAC values that incorporate the date, region, service, and request details.

// Simplified AWS Sig V4 signing key derivation
function getSignatureKey(key, dateStamp, region, service) {
    const kDate = hmacSHA256('AWS4' + key, dateStamp);
    const kRegion = hmacSHA256(kDate, region);
    const kService = hmacSHA256(kRegion, service);
    const kSigning = hmacSHA256(kService, 'aws4_request');
    return kSigning;
}

// The final signature
const signingKey = getSignatureKey(secretKey, '20260327', 'us-east-1', 's3');
const signature = hmacSHA256(signingKey, stringToSign).toString('hex');

This chained HMAC approach means that even if one signing key is compromised, it is only valid for a specific date, region, and service combination. The design limits the blast radius of a key compromise.

Custom API Authentication

If you are building your own API, you can implement a simple HMAC-based authentication scheme. The client includes a signature computed over the request method, path, timestamp, and body. The server verifies this signature using the shared secret.

// Client-side: Sign the request
function signRequest(method, path, body, secret) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const payload = [method, path, timestamp, body].join('\n');
    const signature = hmacSHA256(secret, payload);

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

// Server-side: Verify the request
function verifyRequest(req, secret) {
    const timestamp = req.headers['x-timestamp'];
    const signature = req.headers['x-signature'];

    // Check timestamp is within 5 minutes to prevent replay attacks
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp)) > 300) {
        return false;
    }

    const payload = [req.method, req.path, timestamp, req.body].join('\n');
    const expected = hmacSHA256(secret, payload);

    return timingSafeEqual(signature, expected);
}

7. Implementing HMAC in Different Languages

Every major programming language provides built-in or standard library support for HMAC. Here are examples in the most common languages used in web development.

JavaScript (Web Crypto API)

Modern browsers provide the Web Crypto API, which includes native HMAC support through crypto.subtle. This is what our HMAC Generator tool uses.

async function generateHMAC(message, secret) {
    const encoder = new TextEncoder();
    const keyData = encoder.encode(secret);
    const messageData = encoder.encode(message);

    const key = await crypto.subtle.importKey(
        'raw',
        keyData,
        { name: 'HMAC', hash: 'SHA-256' },
        false,
        ['sign']
    );

    const signature = await crypto.subtle.sign('HMAC', key, messageData);
    const hashArray = Array.from(new Uint8Array(signature));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

// Usage:
const hmac = await generateHMAC('Hello, World!', 'my-secret-key');
console.log(hmac);

Node.js

const crypto = require('crypto');

function generateHMAC(message, secret) {
    return crypto
        .createHmac('sha256', secret)
        .update(message)
        .digest('hex');
}

const hmac = generateHMAC('Hello, World!', 'my-secret-key');
console.log(hmac);

Python

import hmac
import hashlib

def generate_hmac(message, secret):
    return hmac.new(
        secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

result = generate_hmac('Hello, World!', 'my-secret-key')
print(result)

Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func generateHMAC(message, secret string) string {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    return hex.EncodeToString(mac.Sum(nil))
}

func main() {
    result := generateHMAC("Hello, World!", "my-secret-key")
    fmt.Println(result)
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class HMACExample {
    public static String generateHMAC(String message, String secret) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
        mac.init(keySpec);
        byte[] hmacBytes = mac.doFinal(message.getBytes("UTF-8"));
        StringBuilder hex = new StringBuilder();
        for (byte b : hmacBytes) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }
}

8. Security Considerations

While HMAC is a well-designed and proven construction, improper implementation can introduce vulnerabilities. Here are the most important security considerations when working with HMAC.

Constant-Time Comparison

When verifying an HMAC signature, you must use a constant-time comparison function rather than a simple string equality check (===). A regular comparison returns early on the first mismatched character, which creates a timing side-channel. An attacker can measure response times to determine how many characters of the HMAC are correct, effectively brute-forcing the signature one byte at a time.

// WRONG: Vulnerable to timing attacks
if (computedHMAC === expectedHMAC) { /* OK */ }

// CORRECT: Constant-time comparison
const crypto = require('crypto');
if (crypto.timingSafeEqual(
    Buffer.from(computedHMAC),
    Buffer.from(expectedHMAC)
)) { /* OK */ }

Key Management

The security of HMAC depends entirely on the secrecy of the key. Never hardcode keys in source code, commit them to version control, or expose them in client-side JavaScript. Store keys in environment variables, secret management services (AWS Secrets Manager, HashiCorp Vault), or encrypted configuration files.

Key Length

The HMAC specification recommends that keys be at least as long as the hash output (32 bytes for SHA-256, 64 bytes for SHA-512). Shorter keys are padded internally, but longer keys provide better security. Generate keys using a cryptographically secure random number generator -- never use passwords or predictable values as HMAC keys.

Replay Attack Prevention

HMAC alone does not prevent replay attacks. An attacker who captures a valid request with its HMAC signature can resend it later. To prevent this, include a timestamp or nonce in the signed data and reject requests with timestamps outside an acceptable window (typically 5 minutes). Stripe's webhook signing includes timestamps for exactly this reason.

Signature Scope

Sign everything that matters. If you only sign the request body but not the URL or method, an attacker could redirect a POST body to a different endpoint. Sign the HTTP method, URL path, query parameters, content type, timestamp, and body for comprehensive protection.

9. Best Practices

Use HMAC-SHA256 as the Default

Unless you have a specific requirement for a different algorithm, use HMAC-SHA256. It is the industry standard, widely supported, and provides strong security. Do not use HMAC-MD5 or HMAC-SHA1 in new implementations.

Generate Strong Keys

Use a cryptographically secure random number generator to create HMAC keys. In Node.js: crypto.randomBytes(32).toString('hex'). In Python: secrets.token_hex(32). Never derive keys from passwords or use predictable values.

Always Use Constant-Time Comparison

This cannot be emphasized enough. Every HMAC verification must use constant-time comparison. The performance difference is negligible, but the security difference is enormous. Use crypto.timingSafeEqual() in Node.js, hmac.compare_digest() in Python, or hmac.Equal() in Go.

Include Timestamps

Always include a timestamp in the signed data and validate that the timestamp is within an acceptable window (typically 5 minutes). This prevents replay attacks where an attacker resends a captured valid request.

Log and Monitor

Log failed signature verifications (without logging the actual secret key). Monitoring for spikes in failed verifications can alert you to potential attacks. But be careful not to log the raw signature values -- an attacker with access to logs could use them for offline analysis.

Rotate Keys Periodically

Implement a key rotation strategy. Support multiple active keys during the rotation period so that requests signed with the old key are still accepted while the new key is being deployed. Most webhook providers support configuring new secrets while keeping old ones active temporarily.

10. Using Our HMAC Generator Tool

Our free HMAC Generator tool makes it easy to compute HMAC signatures for testing and debugging. It uses the Web Crypto API (crypto.subtle) built into your browser for secure, native HMAC computation.

The tool supports SHA-256, SHA-384, and SHA-512 algorithms with output in both hexadecimal and base64 encoding. You can enter your secret key as plain text (UTF-8) or as a hex-encoded string. The HMAC is computed in real time as you type, so you get instant feedback.

The Verify Mode feature is particularly useful for debugging webhook signatures. Paste the expected HMAC from a webhook header and the tool will instantly tell you whether it matches the computed value. This saves time when troubleshooting API integrations.

All computation happens entirely in your browser. Your secret key and message data are never sent to any server. The tool works offline once the page has loaded. You can verify this by checking the Network tab in your browser's developer tools.

Try the HMAC Generator

Generate and verify HMAC signatures instantly. Free, no sign-up required.

Open HMAC Generator →