Skip to content
apikey.tools
HMACSignatures

HMAC Request Signature Generator

Compute HMAC-SHA1, SHA-256, SHA-384 and SHA-512 signatures, and verify one you were sent.

HMACRuns in this tabInput not transmitted

Input

Sign the exact bytes you transmit. A re-serialised object produces a different signature.

GitHub prefixes with `sha256=`. Stripe uses `v1=` inside a compound header.

Output

computing

HMAC answers a question a plain hash cannot: was this message produced by someone who holds the secret, and has it been modified since? Because the key is mixed into the computation twice, an attacker who can see the message and the signature still cannot produce a signature for a different message.

Sign the exact bytes

This is where implementations break, and it breaks silently. A framework that parses JSON before your handler runs gives you an object. JSON.stringify of that object is not byte-identical to what was sent — key order may differ, whitespace is gone, Unicode may be escaped differently. The signature will not match, and the error will look like a wrong secret.

Capture the raw body:

javascript
// Express: keep the raw bytes alongside the parsed body
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; },
}));

// Next.js route handler: read the text before parsing
const raw = await request.text();
const parsed = JSON.parse(raw); // parse after signing check

Compare in constant time

A naive comparison returns as soon as two bytes differ. That timing difference is measurable over enough requests, and it lets an attacker recover the correct signature one byte at a time.

javascript
import { timingSafeEqual } from "node:crypto";

const a = Buffer.from(expected, "hex");
const b = Buffer.from(received, "hex");
const valid = a.length === b.length && timingSafeEqual(a, b);

The comparison on this page is constant-time for the same reason.

Encoding and prefixes

The signature is bytes; how you transmit it is a convention:

  • Hex — lowercase, twice as long, unambiguous. Most common.
  • Base64 — shorter. Shopify uses it.
  • Prefixed — GitHub sends sha256= before the hex, which future-proofs the header against an algorithm change.

Any of these is fine; both sides must agree exactly.

Key length

The key should be at least as long as the hash output — 32 bytes for SHA-256. HMAC hashes keys longer than the block size before use, so a 1024-bit key gains nothing. Keys shorter than 128 bits are searchable offline by an attacker holding one signed message.

HMAC is not a signature in the public-key sense

It is symmetric. Both parties hold the same secret, so either could have produced any given signature. That is fine for verifying a webhook came from a provider you share a secret with. It cannot provide non-repudiation — for that you need an asymmetric signature from the RSA key pair generator.

Frequently asked questions

Which hash should I use?

SHA-256. SHA-1 inside HMAC is not currently broken — HMAC does not require collision resistance the way a plain signature does — but there is no reason to choose it for anything new, and it fails audits.

Why does my signature not match the provider's?

In order of likelihood: you signed a re-serialised body rather than the raw bytes; the provider signs a constructed string like timestamp.body rather than the body alone; you are hex-encoding where they expect Base64; or the secret has a trailing newline. The webhook signature verifier shows the exact bytes each provider signs.

Should the timestamp be part of the signed payload?

Yes, if you want replay protection. Signing timestamp.body and rejecting old timestamps is what stops a captured request being replayed indefinitely. Stripe and Slack both do this.

Can I use HMAC to authenticate API requests generally?

Yes, and it is stronger than a bearer token: the key is never transmitted, so intercepting a request does not yield the credential. AWS SigV4 works this way — see the SigV4 builder. The cost is a more complex client.

More signatures tools

Compute and verify request signatures