npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

webhook-sig

v0.1.0

Published

Verify and generate webhook signatures locally for Stripe, GitHub, Shopify, Slack, Twilio, and Svix. No network, no telemetry — pure node:crypto.

Downloads

24

Readme

webhook-sig

Verify and generate webhook signatures from your terminal. Stripe, GitHub, Shopify, Slack, Twilio, and Svix — one small CLI, the correct HMAC scheme for each provider.

  • 🔒 100% local. Pure node:crypto. No network calls, no telemetry. Your signing secret never leaves your machine.
  • 📦 Zero dependencies. One file of crypto, one file of CLI. Audit it in five minutes.
  • 🧪 CI-friendly. Reads payloads from stdin or a file, exits 0/1 on verify, speaks --json.
  • ✅ Correct. Tested against the official documented test vectors (GitHub Hello, World!, the Svix example, Slack's base string) and round-tripped generate → verify.
npx webhook-sig verify --provider github \
  --secret "$GH_WEBHOOK_SECRET" \
  --signature "$X_HUB_SIGNATURE_256" \
  --file captured-payload.json
# ✓ VALID — GitHub signature matches.

Why this exists

If you've ever built a webhook handler, you've hit this: the signature doesn't match and you have no idea why. Is it the secret? The wrong base string? A re-serialized body? Each provider does it differently — Stripe signs timestamp.body, Slack signs v0:timestamp:body, Twilio signs the URL plus alpha-sorted form params with SHA-1, Svix base64-decodes the secret first.

webhook-sig lets you:

  • Verify a captured webhook against your secret, so you can prove whether the signature itself is right (vs. your framework mangling the body).
  • Generate a correctly-signed header for a test payload, so you can curl your own handler in development and in CI without waiting for a real event.

It mirrors EventDock's free in-browser signature tools as a scriptable, terminal-native CLI.

Install

No install needed:

npx webhook-sig --help

Or install globally:

npm install -g webhook-sig
webhook-sig --help

Requires Node.js 18+.

Usage

webhook-sig verify   --provider <name> --secret <secret> --signature <header> [payload] [options]
webhook-sig generate --provider <name> --secret <secret> [payload] [options]

The payload is the raw request body — the exact bytes the provider sent. Provide it with --body "<string>", --file <path>, or pipe it on stdin. (For Twilio, see below.)

⚠️ The #1 reason signatures fail: verifying against a re-serialized body. Most frameworks parse JSON before your handler runs, and re-stringifying changes whitespace and key order — which changes the HMAC. Always capture and verify the raw bytes.

Stripe — Stripe-Signature: t=…,v1=…

Signs "{timestamp}.{body}" with HMAC-SHA256, hex. Header carries the timestamp; verification enforces a 5-minute replay window by default.

# Verify
webhook-sig verify --provider stripe \
  --secret whsec_xxx \
  --signature "t=1614000000,v1=5257a8..." \
  --file payload.json
  # add --tolerance 0 to skip the replay-window check for an old captured event

# Generate a test header (timestamp defaults to now)
echo -n '{"id":"evt_1","object":"event"}' | \
  webhook-sig generate --provider stripe --secret whsec_test --header
# Stripe-Signature: t=1718000000,v1=...

GitHub — X-Hub-Signature-256: sha256=…

HMAC-SHA256 of the raw body, hex, prefixed sha256=.

webhook-sig verify --provider github \
  --secret "$GH_WEBHOOK_SECRET" \
  --signature "sha256=757107ea..." \
  --file payload.json

webhook-sig generate --provider github --secret hunter2 --body '{"action":"opened"}'
# sha256=...

Shopify — X-Shopify-Hmac-Sha256: <base64>

HMAC-SHA256 of the raw body, base64-encoded, keyed with your app's API secret.

webhook-sig verify --provider shopify \
  --secret "$SHOPIFY_API_SECRET" \
  --signature "9Xx...=" \
  --file order.json

Slack — X-Slack-Signature: v0=… (+ X-Slack-Request-Timestamp)

Signs "v0:{timestamp}:{body}" with HMAC-SHA256, hex, prefixed v0=. You must pass the timestamp.

webhook-sig verify --provider slack \
  --secret "$SLACK_SIGNING_SECRET" \
  --signature "v0=a2114d57..." \
  --timestamp 1531420618 \
  --body 'token=xyz&team_id=T1&command=%2Fweather'

Twilio — X-Twilio-Signature: <base64>

The odd one out. For classic application/x-www-form-urlencoded webhooks, Twilio signs the full request URL with the POST params appended in alphabetical order (key + value, no separators), using HMAC-SHA1, base64-encoded, keyed with your Auth Token.

Pass the URL and params and let the CLI build the canonical string:

webhook-sig verify --provider twilio \
  --secret "$TWILIO_AUTH_TOKEN" \
  --url "https://your.app/sms" \
  --params '{"From":"+14155551212","Body":"hi","MessageSid":"SM123"}' \
  --signature "GvWf8...="

…or pass the exact pre-built "URL + sorted params" string as --body.

Svix / Standard Webhooks — svix-signature: v1,… (+ svix-id, svix-timestamp)

Used by Svix and providers built on it. Signs "{id}.{timestamp}.{body}" with HMAC-SHA256, base64; the key is the base64-decoded secret after the whsec_ prefix. The header may be a space-separated list of v1,<sig> pairs.

webhook-sig verify --provider svix \
  --secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw \
  --id msg_p5jXN8AQM9LWM0D4loKWxJek \
  --timestamp 1614265330 \
  --signature "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=" \
  --body '{"test": 2432232314}'

Options

| Flag | Description | |------|-------------| | --provider <name> | stripe, github, shopify, slack, twilio, svix | | --secret <secret> | Signing secret / auth token | | --secret-file <p> | Read the secret from a file (keeps it out of shell history) | | --body <string> | Inline raw payload | | --file <path> | Read raw payload from a file | | (stdin) | If neither --body nor --file is given, payload is read from stdin | | --signature <hdr> | (verify) the signature header value the provider sent | | --timestamp <unix> | Stripe/Slack/Svix timestamp (generate: defaults to now) | | --id <id> | Svix message id | | --tolerance <secs> | Replay window for time-based providers (default 300, 0 disables) | | --url, --params | Twilio: request URL and POST params (JSON object) | | --header | (generate) print the full Header-Name: value line | | --json | Machine-readable JSON output | | --quiet | No text; exit code only | | -h, --help / -v, --version | |

Exit codes: 0 = valid signature / success, 1 = invalid signature, 2 = usage error.

Use it in CI

# Fail the job if a recorded fixture's signature doesn't verify
webhook-sig verify --provider stripe --secret "$STRIPE_WHSEC" \
  --signature "$(cat fixtures/sig.txt)" --file fixtures/event.json --tolerance 0 --quiet \
  || { echo "Signature broke!"; exit 1; }

Use it as a library

The signing/verification schemes are exported, with zero dependencies:

import { PROVIDERS, getProvider } from 'webhook-sig';

const { header } = PROVIDERS.github.sign({ body: '{"hi":1}', secret: 'k' });
const { valid } = getProvider('github').verify({ body: '{"hi":1}', secret: 'k', header });

Privacy & security

  • No network. This tool never opens a socket. You can run it offline.
  • No telemetry. Nothing is collected, ever.
  • Comparisons use constant-time equality (crypto.timingSafeEqual).
  • Prefer --secret-file or stdin over --secret so secrets don't land in shell history or process listings.

Going to production?

webhook-sig checks one request at a time — perfect for debugging and CI. But a single signature check is the easy part of receiving webhooks. In production you also have to deal with:

  • Retries when your endpoint is briefly down (and providers give up after a few attempts).
  • A dead-letter queue so a bad deploy doesn't silently drop events forever.
  • Replay to re-process events after you ship a fix.

That's what EventDock does. Point your providers at EventDock, verify signatures once at the edge, and get automatic retries, a DLQ, and one-click replay for your incoming webhooks. Free for 5,000 events/month — no credit card.

→ eventdock.app

License

MIT © EventDock