DTTooleras

How to Use HMAC for API Authentication and Webhook Verification

A practical guide to HMAC-based authentication — how it works, implementing webhook signature verification, and securing API requests with HMAC-SHA256.

DevToolsHub Team18 min read439 words

What is HMAC?

HMAC (Hash-based Message Authentication Code) combines a cryptographic hash function with a secret key to provide both data integrity and authentication. Unlike a plain hash, HMAC requires knowledge of the secret key to generate or verify the signature.

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

The result is a fixed-size signature that proves:

  1. The message has not been tampered with (integrity)
  2. The sender knows the secret key (authentication)

How Webhook Verification Works

When a service (Stripe, GitHub, Slack) sends a webhook to your server, it signs the payload with HMAC:

Service → Your Server:
  POST /webhooks/stripe
  Body: {"event": "payment.completed", "amount": 9999}
  Header: Stripe-Signature: sha256=a1b2c3d4e5f6...

Your server verifies the signature:

const crypto = require("crypto");

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

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

app.post("/webhooks/stripe", (req, res) => {
  const signature = req.headers["stripe-signature"];
  const payload = req.body; // raw body string

  if (!verifyWebhook(payload, signature, process.env.STRIPE_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process the webhook...
  res.json({ received: true });
});

Implementing HMAC in Different Languages

JavaScript (Node.js)

const crypto = require("crypto");

const hmac = crypto
  .createHmac("sha256", "your-secret-key")
  .update("message to sign")
  .digest("hex");

Python

import hmac
import hashlib

signature = hmac.new(
    b"your-secret-key",
    b"message to sign",
    hashlib.sha256
).hexdigest()

Browser (Web Crypto API)

async function generateHmac(message, secret) {
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const signature = await crypto.subtle.sign(
    "HMAC",
    key,
    encoder.encode(message)
  );
  return Array.from(new Uint8Array(signature))
    .map(b => b.toString(16).padStart(2, "0"))
    .join("");
}

Security Best Practices

  1. Use timing-safe comparison — Never use === to compare signatures. Use crypto.timingSafeEqual() to prevent timing attacks.

  2. Include a timestamp — Add a timestamp to the signed payload and reject requests older than 5 minutes to prevent replay attacks.

  3. Use SHA-256 or higher — HMAC-MD5 and HMAC-SHA1 are deprecated. Use HMAC-SHA256 at minimum.

  4. Rotate secrets regularly — Change webhook secrets periodically and support multiple active secrets during rotation.

  5. Verify the raw body — Parse the request body BEFORE any middleware modifies it. JSON parsing can change whitespace.

Generate and verify HMAC signatures with our HMAC Generator tool.

Related Tools

hmacwebhook verificationapi authenticationhmac-sha256webhook signatureapi security

Related articles

All articles

Practice with free tools

200+ free developer tools that run in your browser.

Browse all tools →