HMAC-SHA256 generator: compute an HMAC online in hex or base64

Free HMAC generator: enter a message and a secret key to get the HMAC-SHA256, SHA-1, SHA-384 or SHA-512 instantly in hex, base64 and base64url. It runs in your browser with Web Crypto and uploads nothing. An HMAC-SHA256 is 32 bytes, or 64 hex characters.

This HMAC-SHA256 generator takes your message and secret key and computes the HMAC in hex, base64, and base64url right in your browser as you type. The form starts loaded with RFC 4231 test case 2, so you can check the first result straight against the published answer.

HMAC-SHA256: 32 bytes

Hex
Base64
Base64url, no padding

How to generate an HMAC-SHA256

  1. Paste the message using the exact bytes that get signed, with no extra whitespace or trailing newline.
  2. Enter your secret key and pick its format (text, hex, or base64).
  3. Pick the hash algorithm (SHA-256 is what most webhook providers use).
  4. Copy the hex, base64, or base64url result.

The output length depends only on the algorithm you pick, not on the length of the message or the key.

AlgorithmOutputHex charactersBase64 characters
HMAC-SHA120 bytes4028
HMAC-SHA25632 bytes6444
HMAC-SHA38448 bytes9664
HMAC-SHA51264 bytes12888

HMAC-SHA256 in Node.js, Python, PHP and the shell

Every example below uses RFC 4231 test case 2 (key Jefe, message what do ya want for nothing?) and prints 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843, so you can confirm your setup works before signing real data. The shell one-liner uses printf instead of echo, since echo tacks on a trailing newline and changes the hash.

Node.js

import { createHmac } from "node:crypto";

const mac = createHmac("sha256", "Jefe")
  .update("what do ya want for nothing?")
  .digest("hex");
console.log(mac);

Python

import hashlib
import hmac

mac = hmac.new(b"Jefe", b"what do ya want for nothing?",
               hashlib.sha256)
print(mac.hexdigest())

PHP

<?php
echo hash_hmac('sha256', 'what do ya want for nothing?', 'Jefe');

Shell

printf '%s' 'what do ya want for nothing?' \
  | openssl dgst -sha256 -hmac 'Jefe'

Hex, base64 or base64url

An HMAC is raw bytes, and hex and base64 are just two ways to write those same bytes as text, so the same HMAC looks completely different in each (5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 in hex is W9zBRr9gdU5qBCQmCJV1x1oAPwidJzmDnexYuWTsOEM= in base64). base64url swaps + for - and / for _ and usually drops the = padding at the end, which is how JWT HS256 signatures are written.

Webhook providers don't all pick the same encoding or even sign the same string: GitHub signs the raw body as hex in X-Hub-Signature-256, Shopify signs the raw body as base64 in X-Shopify-Hmac-Sha256, and Stripe and Slack both prepend a timestamp to the body before signing and send hex in Stripe-Signature and X-Slack-Signature.

ProviderHeaderSigned messageEncoding
StripeStripe-Signature (t=...,v1=...)Timestamp, a dot, the raw bodyhex
GitHubX-Hub-Signature-256 (sha256=...)Raw bodyhex
ShopifyX-Shopify-Hmac-Sha256Raw bodybase64
SlackX-Slack-Signature (v0=...)v0:, timestamp, :, raw bodyhex

Why your HMAC doesn't match

  • A trailing newline or other changed bytes crept into the message: piping echo instead of printf '%s' into openssl dgst -sha256 -hmac 'Jefe' changes the RFC test result to 8cc1a9739eea9fe97321dba825363677fed3f8cbc330fa892ad5466a7fd5438e.
  • You're comparing a hex output against a base64 signature.
  • The key is hex or base64, and you passed it in as plain UTF-8 text (or the other way around).
  • Your server parsed the JSON body and re-serialized it before hashing, which alters whitespace or key order.
  • You're using the wrong signing secret, like a test secret on a live endpoint or an old key that was rotated.
  • You signed only the raw body, while the provider actually signs a timestamp plus the body.

If you're debugging a live webhook, you can paste the header and payload into the HMAC signature verifier to check a real provider signature directly.

HMAC vs a plain SHA-256 hash

A plain SHA-256 hash has no key, so anyone can compute it for any message; an HMAC mixes in a secret key, which is why webhook providers use it to prove a delivery came from them. HMAC isn't encryption, though: the message still travels in the clear, and the hash can't be reversed to get the message back. When your server checks a signature, compare the two values in constant time with crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python.

To test your code against a real signed delivery, point the provider at a free bin on webhooks.sh, grab the raw body and signature header like Stripe-Signature or X-Hub-Signature-256, and walk through the webhook security guide.

FAQ

What is an HMAC-SHA256 generator?

It's a tool that computes a keyed hash (HMAC) from a message and a secret key using the SHA-256 algorithm. The output is always 32 bytes, which comes out to 64 hex characters or 44 base64 characters with padding.

Is it safe to paste a secret key into this tool?

Yes, the HMAC calculator runs in your browser with the Web Crypto API (crypto.subtle.importKey and crypto.subtle.sign), and your key and message are never sent anywhere. For production secrets, you can also just run one of the local code examples above.

Can an HMAC be decrypted or reversed?

No. An HMAC is a one-way keyed hash, not encryption, so there's no way to turn the hash back into the original message.

What's the difference between HMAC-SHA256 and SHA-256?

Anyone can compute a plain SHA-256 hash because it doesn't take a key. HMAC-SHA256 folds a secret key into the hash, so only someone with the key can generate or verify the signature.

Why is my HMAC different from the expected value?

Most of the time it's modified bytes in the message (like a trailing newline from echo or re-serialized JSON), comparing hex to base64, treating a hex or base64 key as plain text, using the wrong secret, or signing only the body when the provider also includes a timestamp.

How do I verify a webhook signature with HMAC?

Compute the HMAC over the exact raw request body (plus the timestamp, if the provider includes one) using your signing secret, encode it in the format the provider uses, and compare the two strings in constant time. Our verifier tool handles the provider-specific rules for Stripe, GitHub, Shopify, and Slack automatically.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.