HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogHMAC Authentication: Signing API Requests the Right Way
Table of Contents▾
  • What Is HMAC?
  • HMAC vs Other Authentication Methods
  • Signing Requests in JavaScript
  • Verifying Signatures (Critical: Use Constant-Time Comparison)
  • Preventing Replay Attacks
  • Verifying GitHub Webhooks
  • Choosing the Right Hash Function
  • Generate and Test HMAC Signatures
security#hmac#security#api

HMAC Authentication: Signing API Requests the Right Way

Learn how HMAC works, how to sign API requests for webhooks and APIs, and how to verify signatures securely in your backend code.

Trong Ngo
February 28, 2026
4 min read

HMAC (Hash-based Message Authentication Code) is the backbone of API security for webhooks, payment APIs, and any system where you need to prove that a message came from a trusted sender and wasn't tampered with. Stripe, GitHub, Twilio, and AWS all use HMAC signatures.

What Is HMAC?

HMAC combines a secret key with a message and a cryptographic hash function to produce a signature:

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

In practice, you don't implement this yourself. You use your language's built-in crypto library. The important properties:

  • Only someone with the shared secret key can generate a valid signature
  • Any change to the message produces a completely different signature
  • It's non-reversible — you can't extract the key from the signature

HMAC vs Other Authentication Methods

MethodWhat it provesReplay safeTamper-proof
API Key in headerIdentity (key present)NoNo
Bearer token (JWT)Identity + claimsDepends on expYes (signature)
HMAC signatureIdentity + message integrityDepends on timestampYes
mTLSIdentity (certificate)YesYes

HMAC is ideal for webhooks and server-to-server communication where both sides share a secret.

Signing Requests in JavaScript

const crypto = require('crypto');

function signRequest(secret, body) {
  return crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');
}

// When sending a webhook
const body = JSON.stringify(payload);
const signature = signRequest(process.env.WEBHOOK_SECRET, body);

await fetch(webhookUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Signature-256': `sha256=${signature}`,
  },
  body,
});

Verifying Signatures (Critical: Use Constant-Time Comparison)

Never use === or == to compare signatures. These are vulnerable to timing attacks — an attacker can infer the signature one byte at a time by measuring how long comparison takes.

Always use crypto.timingSafeEqual:

function verifySignature(secret, body, receivedSignature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');

  // Remove prefix if present (e.g., 'sha256=')
  const clean = receivedSignature.replace(/^sha256=/, '');

  // Constant-time comparison
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(clean, 'hex');

  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

Preventing Replay Attacks

A valid HMAC signature proves the message is authentic, but doesn't prevent someone from resending the same valid request. Add a timestamp:

// Sender
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = `${timestamp}.${JSON.stringify(body)}`;
const signature = signRequest(secret, payload);

// Send both timestamp and signature
headers['X-Timestamp'] = timestamp;
headers['X-Signature'] = signature;

// Receiver
function verifyWithReplayProtection(secret, body, timestamp, signature) {
  // Reject requests older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error('Request expired');
  }

  const payload = `${timestamp}.${body}`;
  return verifySignature(secret, payload, signature);
}

This is exactly how Stripe's webhook verification works.

Verifying GitHub Webhooks

// Next.js API route
export async function POST(req) {
  const body = await req.text(); // raw body — don't parse JSON first
  const sig = req.headers.get('x-hub-signature-256');

  const isValid = verifySignature(process.env.GITHUB_WEBHOOK_SECRET, body, sig);

  if (!isValid) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);
  // process event...
}

Important: Always read the raw body before JSON parsing. Parsing and re-serializing changes whitespace, breaking the signature.

Choosing the Right Hash Function

AlgorithmDigest sizeStatus
HMAC-MD5128 bitsDeprecated — don't use
HMAC-SHA1160 bitsLegacy only (still safe for HMAC, but avoid new usage)
HMAC-SHA256256 bits✅ Recommended
HMAC-SHA512512 bits✅ Higher security, larger output

Use HMAC-SHA256 for new implementations. It's the industry standard and supported everywhere.

Generate and Test HMAC Signatures

Use HeoLab's HMAC Generator to quickly generate and verify HMAC signatures without writing code — useful for testing webhook integrations and debugging signature mismatches.

Try These Tools

HMAC Generator

Generate HMAC-SHA256 and HMAC-SHA512 signatures for message authentication and API request signing.

Related Articles

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read

API Key Security: Design, Storage, and Rotation Best Practices

4 min read

YAML vs JSON: When to Use Each and How to Convert Between Them

3 min read

Back to Blog

Table of Contents

  • What Is HMAC?
  • HMAC vs Other Authentication Methods
  • Signing Requests in JavaScript
  • Verifying Signatures (Critical: Use Constant-Time Comparison)
  • Preventing Replay Attacks
  • Verifying GitHub Webhooks
  • Choosing the Right Hash Function
  • Generate and Test HMAC Signatures

Related Articles

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read

API Key Security: Design, Storage, and Rotation Best Practices

4 min read

YAML vs JSON: When to Use Each and How to Convert Between Them

3 min read