HMAC for API request signing
Sumit Jha · Updated 2026-11-06
HMAC is a keyed hash. You and the server share a secret, you both build the same byte string, and you both run the same algorithm. The result proves the holder of the secret produced that string. It does not hide the body. If the MAC differs, compare the canonical string before you blame SHA-256.
The message is an exact byte string
Providers sign a canonical request: method, path, timestamp, and a hash of the body, joined with newlines they specify. A trailing newline, a reordered query, or a pretty-printed JSON body changes the MAC. Hex-encoding the key when the server treats the key as UTF-8 also changes it. The algorithm name has to match. SHA-256 and SHA-512 are not interchangeable.
Output encoding is a separate choice. The same MAC can be printed as hex or as Base64. Comparing those strings as text fails even when the MAC is correct.
HMAC is not a JWT and not encryption
A JWT that uses HS256 contains an HMAC over the signing input, plus the header and payload in the clear. Computing a raw HMAC of the body is a different construction. Encryption would hide the body. HMAC does not.
Webhook receivers are a common place this shows up. The provider signs the raw body. If you parse JSON and re-serialize it before checking the signature, verification fails. On DevTooKit, the HMAC tool runs locally, which is what you want for a secret. The webhook inbox is different: posted bodies are stored for the session so the browser can display them. Do not send production customer events there.
Build the signed string on purpose
Pick a secret, the message POST\n/v1/hooks\n1700000000\n and the SHA-256 HMAC, and write the hex. Change the timestamp by one digit and hash again. The hex should change completely. That avalanche is normal. What is not normal is a mismatch when you believe the strings are equal. Put both canonical strings in a diff. Look for a trailing newline, a space after a colon, or \r\n where the spec shows \n.
Then change only the encoding of the output from hex to Base64 and compare as text. They differ, and both can be correct. Providers state the encoding. Guessing is how a valid signature gets rejected for a week. The key has the same trap: a spec that says “the secret is hex” wants you to decode it to bytes before HMAC. Passing those hex characters as UTF-8 produces a different MAC that looks equally formatted.
A JWT signer with HS256 is HMAC over the two encoded segments, not over the raw JSON. If you HMAC the payload object and paste the result into a JWT signature slot, verification fails. Use the signer when you want a token. Use the HMAC tool when the API asked for a MAC of a canonical request. Neither one encrypts the body, and neither one is a place for a production secret you cannot rotate.
Include a timestamp in the signed string and reject old ones, or a captured request can be replayed. The MAC proves the bytes and the key. It does not prove the request is new. Nonces do the same job when the server remembers them. The DevTooKit HMAC page will not store that nonce for you. It only shows the digest of what you typed, in the encoding you picked. Compare it to a known answer from the provider’s docs before you compare it to production.
FAQ
Why do two correct-looking HMACs differ?
The key encoding, the algorithm, or the signed bytes differ. Pretty-printing JSON is enough to break a match.
Does HMAC encrypt the payload?
No. It authenticates bytes. The message stays readable.
Related tools
- JWT SignerSign JWTs with HS256 / HS384 / HS512 using the browser's native Web Crypto API.
- Webhook ReceiverGet a unique URL to capture and inspect incoming HTTP webhooks in real time.
Limitation: this note describes the DevTooKit tool and the general rule behind it. Production libraries, clocks, and schedulers can differ. Check those docs before you ship.