What HMAC-SHA256 actually proves
A plain SHA-256 hash tells you a message has not changed. It does not tell you who sent it, because anyone can hash anything. HMAC closes that gap by folding a shared secret into the hash, so only someone holding the key can produce a tag that verifies.
The construction is defined in RFC 2104 and it is deliberately not just SHA256(key + message). That naive form is vulnerable to a length-extension attack: given a valid tag, an attacker can append data and forge a new valid tag without ever seeing the key. HMAC hashes twice with two derived keys, which removes the attack:
K' is the key padded to the hash's 64-byte block size, or hashed down first if it is longer. ipad is the byte 0x36 repeated, opad is 0x5C repeated.
Hex or Base64
The tag is 32 raw bytes. What differs between APIs is only how those bytes are written down, and picking the wrong encoding is the usual reason a signature comparison fails when the key and message are both right.
- Hex gives 64 characters. GitHub webhooks (X-Hub-Signature-256), Stripe (Stripe-Signature), Shopify's API and AWS Signature Version 4 all use it.
- Base64 gives 44 characters ending in =. Shopify's webhook header, Twilio, and most JWT and OAuth 1.0a signing use it.
Use the output selector above to switch between them. The underlying tag is the same either way, so if your server computes one and you have the other, you are looking at the same value in two alphabets rather than at a mismatch.
Computing the same value in your own code
Every mainstream runtime ships HMAC-SHA256. These all produce the hex output above.
One warning that costs people hours: echo adds a trailing newline, so echo -n or printf is what you want. A tag over admin\n will never match a tag over admin.
Verifying a webhook signature
The common job. Your endpoint receives a request with a signature header, and you need to decide whether to trust the body. Compute HMAC-SHA256 over the exact raw body with your webhook secret as the key, and compare against the header.
Two details decide whether this works. Sign the raw bytes, before any JSON parse and re-serialise: reordering keys or changing whitespace changes the tag. And compare with a constant-time function such as crypto.timingSafeEqual rather than ===, because a comparison that returns early on the first differing byte leaks how much of a guess was correct.
Several providers put more than the body into the signed string. Stripe signs timestamp + "." + body and expects you to reject old timestamps, which is what stops a captured request being replayed.
Common questions
- Is my secret key sent to a server?
- No. The HMAC is computed in your browser by JavaScript on this page. The key and the message never leave your device, nothing is logged, and the tool keeps working with the network disconnected. That said, a production secret pasted into any web page is a secret that has been in a browser, so for a live key prefer the openssl or Node snippet above.
- What is the difference between HMAC-SHA256 and SHA-256?
- SHA-256 is a hash: it takes a message and returns a fingerprint, and anyone can compute it. HMAC-SHA256 takes a message and a secret key, so the result can only be produced or checked by someone holding the key. SHA-256 detects accidental corruption; HMAC-SHA256 detects tampering by someone who wants to go undetected.
- How long is an HMAC-SHA256 output?
- Always 256 bits, or 32 bytes, however long the message and the key are. Written as hex that is 64 characters; as Base64 it is 44 characters including one padding =.
- Does the key length matter?
- HMAC accepts a key of any length: shorter keys are padded to 64 bytes, longer ones are hashed down to 32 first. Security does depend on it though. RFC 2104 recommends at least 32 bytes of random data, and a short or guessable key can be brute-forced offline from a single captured message and tag.
- Why does my signature not match the one in the header?
- In order of how often it happens: the wrong encoding (hex against Base64), a trailing newline added by a shell echo, signing a re-serialised JSON body instead of the raw bytes received, and a provider that signs a timestamp alongside the body rather than the body alone.
- Is HMAC-SHA256 still considered secure?
- Yes. It is a current recommendation in NIST SP 800-107 and is used throughout TLS, JWT (as HS256), AWS SigV4 and OAuth. HMAC is also unusually robust: it survived in use even against MD5 and SHA-1 long after collisions were found in those hashes, because it does not depend on collision resistance in the way a plain signature does.