Skip to content
FindTool

    HMAC Generator

    Generate HMAC signatures with a secret key for webhooks and API authentication.

    HMAC Generator tool

    The key stays in this tab. It is used through the Web Crypto API and is never sent anywhere, saved, or included in a copied result.

    Signature
      

    Paste the whole header if you like — a GitHub sha256= prefix and a Stripe t=…,v1=… header are both understood.

    What this tool does

    An HMAC proves that whoever produced a message held a shared secret. Give this page a message and a key and it returns the HMAC of the two, defaulting to SHA-256 because that is what nearly every webhook provider uses. Paste a signature you received and it tells you whether it verifies.

    The key is imported through crypto.subtle.importKey() as a non-extractable key and used only to sign inside this tab. Nothing about the key or the message is transmitted, logged or stored — check the network panel if you want to confirm it.

    Common uses

    • Verifying a webhook you received really came from Stripe, GitHub, Shopify or Slack.
    • Reproducing a signature your own service generated, to see why the receiver rejects it.
    • Signing a canonical string for an API that expects one, such as AWS Signature v4.
    • Checking that your code and a provider's SDK agree on the same bytes.

    Verifying a Stripe webhook signature

    Stripe sends a header that looks like this:

    Stripe-Signature: t=1737039600,v1=692bee8484f34eb73c69635675896b98780a40c108235a39cdeb44d86a57e5ad

    t is the Unix timestamp of the attempt and v1 is an HMAC-SHA-256. What gets signed is not the request body on its own but the timestamp, a literal full stop and the raw body:

    1737039600.{"id":"evt_1PrLq2","type":"payment_intent.succeeded"}

    Put that string in the message box, put the endpoint's signing secret (whsec_FindToolExampleSecret for this example) in the key box, and the signature above appears. Press Example to load all three fields at once.

    Three details account for almost every failed verification:

    • Re-serialised JSON. Sign the exact bytes received; parsing and re-encoding changes key order, spacing or escapes, and the HMAC with them.
    • The wrong secret. The signing secret is per endpoint and starts whsec_. Your API key verifies nothing.
    • The missing timestamp. Signing the body alone is the most common mistake, which is why this page warns when a t= value is present and your message does not start with it.

    GitHub's X-Hub-Signature-256 does the same job differently: sha256= followed by an HMAC of the body alone, no timestamp.

    Worth knowing

    Verifying in your own code means two things this page cannot do for you. Compare in constant time — crypto.timingSafeEqual in Node, hmac.compare_digest in Python — because a plain === leaks how much of a forged signature was right. And reject old timestamps: Stripe's libraries default to a five-minute tolerance, which is what stops a captured webhook being replayed later.

    An HMAC authenticates, it does not hide — the message is not encrypted. Because its security rests on the key rather than the hash's collision resistance, HMAC-SHA-1 is still sound, but pick SHA-256 for anything new.

    Frequently asked questions

    Why not just hash the secret and the message together?

    Because SHA-256(secret + message) can be forged without the secret. SHA-1 and the SHA-2 family are Merkle–Damgård constructions, so the digest is the internal state at the end of the input: an attacker who has it and knows the input length can continue hashing, appending bytes and producing a valid digest for the longer message. Flickr’s API fell to this in 2009. The nested passes defined in RFC 2104 close the hole.

    How long should the signing key be?

    RFC 2104 §3 sets the floor at the digest length — 32 bytes for HMAC-SHA-256 — and there is a ceiling as well: a key longer than the hash’s 64-byte block (128 bytes for SHA-512) is itself hashed down to 32 bytes before use, so the surplus characters contribute nothing. A 200-character passphrase is no stronger than 32 bytes from the random string generator.

    Why does my code produce a different HMAC from the library’s?

    Nearly always because the key is being read as the wrong bytes. A secret published as 64 hex characters is 32 bytes of key material; signing with those 64 characters as UTF-8 text yields a completely different and completely wrong value, and the same trap catches Base64-encoded secrets. Some schemes never sign with the secret directly either: AWS Signature v4 chains four HMACs over AWS4 plus the secret, the date, the region and the service.