Devkitr
Encoding & Security

HMAC Authentication — How to Sign and Verify API Requests

2026-02-258 min readby Kashif

HMAC (Hash-based Message Authentication Code) is a mechanism for verifying both the integrity and authenticity of a message. It is used extensively in API security — Stripe, GitHub, Twilio, Slack, and AWS all use HMAC to authenticate webhook payloads and API requests.


How HMAC Works


HMAC combines a secret key with a cryptographic hash function (typically SHA-256 or SHA-512):


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


In practice:

  • The server and client share a secret key (never transmitted in requests)
  • The sender computes HMAC = HMAC_SHA256(secret_key, message)
  • The HMAC is sent with the request (in a header or query param)
  • The receiver independently computes the HMAC and compares it

  • If the HMACs match, the message is authentic and untampered.


    HMAC vs Signatures vs API Keys


    | | API Key | HMAC | Asymmetric Sig (RSA/Ed25519) |

    |---|---|---|---|

    | Secret shared? | Yes (the key itself) | Yes (signing secret) | No (private key stays local) |

    | Verifies sender | ✅ | ✅ | ✅ |

    | Verifies integrity | ❌ | ✅ | ✅ |

    | Key exposure risk | High (key = auth) | Medium | Low |

    | Performance | Fast | Fast | Slower |


    Common HMAC Use Cases


    Webhook Verification (Stripe Example)


    Stripe signs every webhook payload with your webhook secret:


    import hmac, hashlib, time


    def verify_stripe_webhook(payload: bytes, sig_header: str, secret: str) -> bool:

    # sig_header looks like: "t=1614556800,v1=abc123..."

    parts = dict(item.split("=", 1) for item in sig_header.split(","))

    timestamp = parts["t"]

    signature = parts["v1"]


    # Prevent replay attacks: reject if > 5 minutes old

    if abs(time.time() - int(timestamp)) > 300:

    return False


    signed_payload = f"{timestamp}.{payload.decode()}"

    expected = hmac.new(

    secret.encode(), signed_payload.encode(), hashlib.sha256

    ).hexdigest()


    return hmac.compare_digest(expected, signature)


    GitHub Webhook Verification


    const crypto = require('crypto');


    function verifyGitHubWebhook(payload, signature, secret) {

    const expected = 'sha256=' + crypto

    .createHmac('sha256', secret)

    .update(payload)

    .digest('hex');


    // Use timingSafeEqual to prevent timing attacks

    return crypto.timingSafeEqual(

    Buffer.from(expected),

    Buffer.from(signature)

    );

    }


    AWS Request Signing (SigV4)


    AWS uses HMAC-SHA256 to sign API requests:


    StringToSign = "AWS4-HMAC-SHA256" + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest

    Signature = HMAC-SHA256(SigningKey, StringToSign)


    Implementing HMAC in Different Languages


    Node.js:

    const crypto = require('crypto');

    const signature = crypto

    .createHmac('sha256', secretKey)

    .update(messageBody)

    .digest('hex');


    Python:

    import hmac, hashlib

    signature = hmac.new(

    secret_key.encode(), message.encode(), hashlib.sha256

    ).hexdigest()


    Go:

    import "crypto/hmac"; "crypto/sha256"; "encoding/hex"

    mac := hmac.New(sha256.New, []byte(secretKey))

    mac.Write([]byte(message))

    signature := hex.EncodeToString(mac.Sum(nil))


    PHP:

    $signature = hash_hmac('sha256', $message, $secretKey);


    Security Best Practices


    1. Always Use Timing-Safe Comparison

    Never use === or == to compare HMAC values — this is vulnerable to timing attacks:


    // ❌ WRONG — vulnerable to timing attacks

    if (computedHmac === receivedHmac) { ... }


    // ✅ CORRECT

    if (crypto.timingSafeEqual(Buffer.from(computedHmac), Buffer.from(receivedHmac))) { ... }


    2. Include a Timestamp and Reject Replays

    Add a timestamp to the signed payload and reject requests older than 5 minutes. Stripe, GitHub, and Slack all do this.


    3. Use HMAC-SHA256 or Stronger

    MD5 and SHA-1 based HMACs are deprecated. Use SHA-256 or SHA-512.


    4. Rotate Secrets Periodically

    Treat HMAC secrets like passwords. Rotate them regularly and immediately if exposed.


    Generate HMAC Signatures Online


    Use our free HMAC Generator to compute HMAC-SHA256 and HMAC-SHA512 signatures instantly in your browser, with your message and secret key — entirely client-side, nothing is sent to any server.


    Related Articles

    Back to Blog