How to Generate a Webhook Secret (Copy-Paste Commands for Linux, macOS, Windows)

Copy-paste terminal commands to generate a cryptographically strong webhook secret on Linux, macOS, and Windows, with no installs, and how to use it in a self-hosted receiver.

Every webhook integration needs a secret: the shared value that lets your receiver prove a request really came from the sender. This guide gives you copy-paste commands to generate a cryptographically strong webhook secret on Linux, macOS, and Windows, with zero installs, plus how to plug the secret into a self-hosted receiver. Every command below was executed and verified before publishing.

What makes a good webhook secret

Three properties matter:

  1. Random from a cryptographic source. The secret must come from your operating system's CSPRNG (/dev/urandom on Linux and macOS, the CNG random provider on Windows). Ordinary random functions, timestamps, and anything you type by hand are guessable.
  2. At least 32 bytes (256 bits) of entropy. That is beyond brute force for any attacker. All commands below produce exactly that.
  3. A safe character set. Hex output (0-9a-f) works everywhere: it is URL-safe, header-safe, shell-safe, and accepted by every webhook provider. Base64 is shorter but its +, / and = characters occasionally break naive integrations, and some providers restrict the charset (Telegram, for example, only allows A-Z a-z 0-9 _ - in its secret token).

A 32-byte hex secret is 64 characters long. That is the format every command below produces.

Linux

Most servers have OpenSSL installed:

openssl rand -hex 32

Example output:

1a6deb7ee231a29499434f437989513bc93c6371dd31cea2e9ab9e634343d477

No OpenSSL, or a minimal container image? This one-liner uses only coreutils, and works even on Alpine and BusyBox:

head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'

Both read from the kernel CSPRNG, so they are equally strong.

macOS

macOS ships LibreSSL, and the same command works out of the box in Terminal:

openssl rand -hex 32

The /dev/urandom one-liner from the Linux section works identically on macOS.

Windows

PowerShell is built into every Windows 10 and 11 machine, and .NET exposes the system CSPRNG directly. Paste this into any PowerShell window (works in both Windows PowerShell 5.1 and PowerShell 7):

$rng=[System.Security.Cryptography.RandomNumberGenerator]::Create(); $b=New-Object byte[] 32; $rng.GetBytes($b); ($b | ForEach-Object ToString x2) -join ''

Example output:

8aa0a80f3dc814c506faa35bcb8bbf1bd837a1ce85f90e68fb2e61b6ed6f448b

If you have Git for Windows installed, you also have OpenSSL: open Git Bash and run openssl rand -hex 32 exactly like on Linux.

Cross-platform one-liners

If you already have one of these runtimes, they work identically on all three operating systems:

Python 3 (the secrets module exists for exactly this purpose):

python3 -c "import secrets; print(secrets.token_hex(32))"

Node.js:

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

Docker (handy on a host where you trust nothing else):

docker run --rm alpine sh -c "head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'"

What not to use

  • uuidgen / GUIDs. A version 4 UUID contains only 122 random bits and its format telegraphs exactly what it is. Not catastrophic, but strictly worse than the commands above for the same effort.
  • Timestamps, hostnames, hashed words. md5sum of the date is not random; anything derived from public or guessable input can be recomputed by an attacker.
  • Online generator websites. A secret that was displayed by someone else's server is not a secret. We wrote up the full reasoning in why you shouldn't use an online webhook secret generator.

Using the secret with Core Webhook Module

Generating the secret is half the job; both sides need it. Here is the receiving side with Core Webhook Module, the open-source self-hosted webhook gateway. Put the webhook definition in webhooks.json:

{
    "orders": {
        "data_type": "json",
        "module": "log",
        "authorization": "Bearer {$WEBHOOK_SECRET}"
    }
}

The {$WEBHOOK_SECRET} syntax reads the value from an environment variable at startup, so the secret never lives in the config file. Pass it when you start the container:

docker run --rm -d --name webhook -p 8099:8000 \
  -v "$PWD/webhooks.json:/app/webhooks.json:ro" \
  -v "$PWD/connections.json:/app/connections.json:ro" \
  -e WEBHOOK_SECRET=paste-your-generated-secret-here \
  spiderhash/webhook:latest

Then verify both directions. A request carrying the right secret succeeds:

curl -i -X POST http://localhost:8099/webhook/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer paste-your-generated-secret-here" \
  -d '{"event":"order.created","id":42}'

Expected: HTTP 200 with {"message":"200 OK"}. A request with the wrong secret gets HTTP 401 and {"detail":"Unauthorized"}, and a request with no Authorization header at all gets HTTP 401 with {"detail":"Invalid Bearer token format: must start with 'Bearer '"}. We ran all three cases against the container before publishing this post.

The same generated value drops into any auth scheme Core Webhook Module supports: a custom header via header_auth (what Telegram's secret_token uses), or an HMAC signing key via the hmac block (what GitHub and Stripe-style signatures use). Once you have the secret in place, read webhook secret best practices for length, storage, and zero-downtime rotation, and pick your sender from our step-by-step guides for GitHub, GitLab, Stripe and more.


Keep reading

Browse more: all integration guides · webhook security · HTTPS setup · documentation · what is Core Webhook Module?

Subscribe to Free Webhook Tool

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe