Advertisement
🪝

Webhook Secret Generator

Generate cryptographically secure HMAC webhook secrets for GitHub, Stripe, Slack, and any webhook platform. Free, instant, browser-only.

Advertisement

What Is a Webhook Secret?

A webhook secret is a shared symmetric key that lets your server verify that an incoming HTTP request genuinely came from a trusted source — and that its payload was not tampered with in transit. When a platform like GitHub, Stripe, or Slack fires a webhook, it computes an HMAC (Hash-based Message Authentication Code) signature over the request body using your secret, and includes that signature in a request header. Your server recomputes the signature and compares the two. If they match, the request is authentic.

Without a webhook secret, any attacker who discovers your endpoint URL can send fake events — fake payment confirmations, fake CI triggers, fake deployments — and your server has no way to tell real from forged.

Why You Need a Cryptographically Secure Webhook Secret

Not all secrets are equal. A short or predictable secret can be brute-forced or guessed. The security of HMAC-SHA256 rests entirely on the secrecy and unpredictability of the key. This means your webhook secret must be:

  • Long enough — at least 128 bits (16 bytes), preferably 256 bits (32 bytes)
  • Randomly generated — from a CSPRNG (cryptographically secure pseudo-random number generator), not Math.random()
  • Never reused across services or endpoints
  • Verified with timing-safe comparison — to prevent timing oracle attacks

This tool generates secrets using the Web Crypto API's crypto.getRandomValues(), which sources entropy from the operating system's CSPRNG — the same source used by password managers, TLS key generation, and SSH key creation. Your secrets are generated entirely in your browser and never transmitted to any server.

GitHub Webhook Secret Format

GitHub accepts any arbitrary string as a webhook secret. The recommended practice is a 64-character lowercase hexadecimal string (32 random bytes). GitHub uses HMAC-SHA256 with this secret to sign each webhook payload and sends the signature in the X-Hub-Signature-256 header in the format sha256=<hex-signature>.

The whsec_ prefix is a human-readable convention — not required by GitHub — that makes secrets easy to identify in config files and environment variables. When verifying, strip any prefix before using the secret as the HMAC key.

GitHub's documentation recommends secrets of at least 20 characters. This generator produces 32-byte (256-bit) secrets by default, which far exceeds that recommendation and is resistant to any foreseeable brute-force attack.

Stripe Webhook Signing Secret

Stripe generates a signing secret for each webhook endpoint in your dashboard (format: whsec_...). When Stripe fires a webhook, it includes a Stripe-Signature header containing a timestamp and one or more HMAC-SHA256 signatures.

The recommended verification approach uses the official Stripe SDK:

const event = stripe.webhooks.constructEvent(
  request.body,          // raw request body as Buffer or string
  request.headers['stripe-signature'],
  process.env.STRIPE_WEBHOOK_SECRET
);

Stripe's signature scheme also includes the timestamp in the signed payload (v1:<timestamp>:<body>), which prevents replay attacks — an attacker cannot re-send a captured webhook after the tolerance window (default 300 seconds) has passed.

Slack Signing Secret

Slack uses a signing secret to verify requests sent to your Slack app's endpoints. The verification string is assembled as v0:<timestamp>:<request body>, then HMAC-SHA256 is computed over that string using your signing secret. The result is compared against the X-Slack-Signature header (format: v0=<hex>).

Slack also sends an X-Slack-Request-Timestamp header. You should reject requests where the timestamp is more than 5 minutes old to prevent replay attacks.

How to Verify Webhook Signatures in Node.js

For platforms that don't provide an SDK, verify signatures manually using Node.js's built-in crypto module:

const crypto = require('crypto');

function verifyWebhookSignature(payload, receivedSig, secret) {
  // Always use timingSafeEqual to prevent timing attacks
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  const expectedBuf = Buffer.from('sha256=' + expected, 'utf8');
  const receivedBuf = Buffer.from(receivedSig, 'utf8');

  if (expectedBuf.length !== receivedBuf.length) return false;

  return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}

// Express example
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-hub-signature-256'];
  if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  // Process verified event
  res.status(200).send('OK');
});

The critical detail is crypto.timingSafeEqual(). A simple string comparison (===) leaks timing information that an attacker can exploit to reconstruct the expected signature byte by byte. Always use a constant-time comparison function.

For more HMAC operations, try our HMAC Generator tool. To generate general-purpose random tokens, see the Random Token Generator. For API authentication keys, the API Key Generator offers additional formats.

How to Use

  1. Select your platform — choose GitHub, Stripe, Slack, Generic, or Custom. Each preset generates secrets in the format that platform expects.
  2. Click Generate — secrets are created instantly using crypto.getRandomValues() in your browser. Nothing is sent to any server.
  3. Copy your secret — click Copy next to the specific format you need, or Copy All to grab everything.
  4. Store it securely — paste the secret into your server's environment variables (e.g., WEBHOOK_SECRET=...), never into source code.
  5. Configure the provider — paste the same secret into your webhook provider's dashboard (GitHub repo settings, Stripe webhook endpoint, Slack App credentials).
  6. Verify in your code — use HMAC-SHA256 with timingSafeEqual() to validate every incoming webhook payload.

Frequently Asked Questions

How long should a webhook secret be?

At minimum 16 bytes (128 bits), but 32 bytes (256 bits) is the industry standard recommended by GitHub, Stripe, and most security guidelines. This generator defaults to 32 bytes, giving you 256 bits of entropy — enough to make brute-force attacks computationally infeasible.

What format does GitHub use for webhook secrets?

GitHub accepts any string as a webhook secret. The common convention is a 64-character lowercase hex string (32 bytes), sometimes prefixed with whsec_ for clarity. GitHub then uses HMAC-SHA256 with that secret to sign each payload, and sends the signature in the X-Hub-Signature-256 header.

How do I verify a Stripe webhook signature?

Use stripe.webhooks.constructEvent(payload, sig, secret) in the official Stripe SDK. If you're verifying manually, compute HMAC-SHA256 over the string 'v1:' + timestamp + ':' + rawBody using your signing secret, then compare with the v1= value in the Stripe-Signature header using a timing-safe comparison.

What is HMAC-SHA256 and why does it matter for webhooks?

HMAC-SHA256 (Hash-based Message Authentication Code using SHA-256) combines your secret key with the request body to produce a fixed-length signature. If the payload is tampered in transit, the signature will not match. This prevents replay attacks and payload forgery — without it, any attacker who knows your endpoint URL can send fake webhook events.

Is it safe to generate webhook secrets in the browser?

Yes. This tool uses the Web Crypto API (crypto.getRandomValues), which is a CSPRNG (cryptographically secure pseudo-random number generator) built into every modern browser. No data is sent to any server — generation happens entirely in your browser tab. The secrets never leave your device unless you paste them somewhere.

What's the difference between a webhook secret and an API key?

An API key authenticates outbound requests you make to a third-party service. A webhook secret authenticates inbound requests a third-party service makes to your server. Both should be kept private, but they serve opposite directions: API keys prove who you are; webhook secrets prove who is calling you.

How do I rotate a webhook secret without downtime?

Generate a new secret with this tool. In your code, temporarily accept signatures from both the old and new secret (dual-validation). Update the secret in your webhook provider's dashboard. Once all in-flight webhooks using the old secret have been processed (usually within a few minutes), remove the old secret from your code.

Can I use the same secret for multiple webhook endpoints?

Technically yes, but it is not recommended. Using a unique secret per endpoint limits blast radius — if one secret is leaked, only that endpoint is compromised. It also makes rotation easier, and some platforms (like Stripe) generate a distinct secret per endpoint automatically.

Comments

No comments yet. Be the first!