HMAC generator

Sign a message with a shared secret and read the HMAC in hex or base64, with the Web Crypto code to verify it on the other side.

Algorithm
Output
HMAC SHA-256
Signing happens in this tab and the secret is never sent anywhere. It is still a secret: use a throwaway value here if the real one belongs to production.
verify.tsts
// Node, and anywhere else with Web Crypto
const key = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(secret),
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign"],
);

const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));

Questions

Why does my webhook signature not match?+

Almost always because the message is not byte for byte what was signed. Sign the raw request body, before any JSON parsing and re-serialising, and check whether the sender prefixes the digest with something such as sha256=.

Hex or base64?+

Whichever the other side expects, they carry the same bytes. Stripe and GitHub use hex; several others use base64.

Is it safe to paste my secret here?+

The signing happens in this tab and nothing is sent anywhere, but a production secret should not be pasted into any web page as a habit. Use a throwaway value to work out the shape, then run the code on the last line.

How do I compare two signatures safely?+

With a constant-time comparison: crypto.timingSafeEqual in Node, hmac.compare_digest in Python. A plain === returns as soon as two bytes differ, and that difference in timing is enough to recover a signature byte by byte.

Related tools

The tools are free. So is most of the library.

1935 blocks and 989 pieces for shadcn/ui and Tailwind, built on the same tokens these tools write. Install one with a command and the code is yours.

No signup for the tools. MIT for free blocks, commercial licence for Pro.

Markdown version