What is a webhook secret? Signing secrets explained

A webhook secret, also called a signing secret, is a random key shared between a webhook provider and your endpoint. The provider uses it to compute an HMAC signature for every delivery, and your endpoint uses the same key to recompute that signature. If they match, the request came from someone who knows the secret and the body wasn't changed.

# .env
STRIPE_WEBHOOK_SECRET=whsec_5a1b9c...        # issued by Stripe, one per endpoint
GITHUB_WEBHOOK_SECRET=9f2c4e7a1b...          # chosen by you when adding the webhook

How a webhook secret is used

  1. When you create the webhook, the provider generates a secret (Stripe, Standard Webhooks providers) or you supply one (GitHub, many self-hosted tools).
  2. For each delivery, the provider signs the body with it: HMAC-SHA256(secret, signed_string).
  3. Your endpoint loads the same secret from its configuration, recomputes the signature over the raw body, and compares.

The secret itself is never sent with the request. Only the signature is.

Secret formats

ProviderFormatHow it's used as the HMAC key
Stripewhsec_...The whole string, prefix included
GitHubAny string you chooseAs-is
ShopifyThe app's client secretAs-is
Standard Webhookswhsec_ + base64Strip whsec_, then base64-decode the rest

That last row catches people out. The Standard Webhooks key is the decoded bytes, not the text after the prefix.

Generating your own secret

When the provider asks you to choose one, generate at least 32 random bytes:

openssl rand -hex 32
# or
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Don't use a password, a word or anything you've used elsewhere.

Storing a webhook secret

  • Keep it out of code. Load it from an environment variable or a secret manager such as AWS Secrets Manager, GCP Secret Manager, Vault or Doppler.
  • One secret per endpoint and environment. Separate secrets for production, staging and local development mean a leaked dev secret can't be used to forge production events.
  • Never log it. Don't print it in errors, and be careful with request-logging middleware that dumps configuration.
  • Restrict access. Only the service that verifies webhooks needs it.

Rotating a webhook secret

Rotate on a schedule and immediately if a secret leaks. To avoid rejecting legitimate deliveries mid-rotation:

  1. Generate the new secret at the provider. Good providers then sign with both secrets for a while. The Webhook Relay Outbound Gateway, for example, signs every delivery with both the new and previous secret for 24 hours, so the webhook-signature header carries two signatures.
  2. Deploy the new secret to your endpoint, or accept either secret during the overlap.
  3. Confirm deliveries verify with the new secret, then remove the old one.

If the provider supports only one secret at a time, make your verifier accept a list of secrets, add the new one, rotate at the provider, then remove the old one.

Common mistakes

  • Using the wrong environment's secret. Stripe test-mode and live-mode secrets differ, and every Stripe endpoint has its own. A mismatch makes every signature fail.
  • Trailing whitespace or quotes. A newline copied into an environment variable silently changes the key.
  • Using the secret text instead of the decoded key for Standard Webhooks.
  • Sharing one secret across many customers when you send webhooks. Each customer endpoint should have its own, so one leak doesn't compromise everyone.
  • Treating a secret in the URL as enough. A token in the query string ends up in access logs and doesn't protect the body from tampering. Sign the body.

To check a secret against a captured request, use the signature verifier or the HMAC calculator. Both run in the browser.

Frequently asked questions

What is a webhook secret?

A webhook secret, or signing secret, is a random key shared between the webhook provider and your endpoint. The provider uses it to compute an HMAC signature for every delivery, and your endpoint uses the same key to recompute the signature and confirm the request is genuine and unmodified.

Where do I find my webhook secret?

In the provider's webhook settings, usually shown when you create the endpoint. Stripe shows a signing secret starting with whsec_ on each endpoint's page. GitHub asks you to choose the secret yourself when you add the webhook. Standard Webhooks providers issue secrets prefixed with whsec_.

Is a webhook secret the same as an API key?

No. An API key authenticates your requests to the provider's API. A webhook secret verifies requests the provider sends to you. Keep them separate, and never use an API key as a webhook secret.

How do I rotate a webhook secret without downtime?

Use a provider that supports overlap. During rotation the provider signs each delivery with both the old and the new secret, or your endpoint accepts either secret for a short window. Deploy the new secret, confirm deliveries verify with it, then retire the old one.