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:
- When you configure a webhook, the provider gives you a secret key
- When they send a webhook, they compute
HMAC-SHA256(secret, request_body) - They include the signature in a request header (e.g.,
X-Signature: sha256=abc123...) - Your server receives the request, recomputes the signature using the same secret and body
- If the signatures match, the webhook is authentic and unmodified
=== 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:
- Build the canonical request string — typically method + URL + headers + body hash
- Sign it with your secret key using HMAC-SHA256
- Include the signature in the
Authorizationheader
// 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
| Algorithm | Output Size | Use Today? |
|---|---|---|
| HMAC-MD5 | 128 bits | Legacy only — MD5 is weak |
| HMAC-SHA1 | 160 bits | Avoid for new systems |
| HMAC-SHA256 | 256 bits | Yes — current standard |
| HMAC-SHA512 | 512 bits | Yes — 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
| Method | Proves Identity | Proves Integrity | Stateless |
|---|---|---|---|
| API Key in header | Yes | No | Yes |
| JWT (HS256) | Yes | Yes (token only) | Yes |
| HMAC request signing | Yes | Yes (full payload) | Yes |