Skip to content

Repository files navigation

hooksig

The jwt.io of webhooks. One consistent reference for how every provider signs a webhook, a live playground that builds the signature in front of you, and a zero-dependency verifier SDK that runs on the edge.

Webhook signing is fragmented. Every provider uses a slightly different canonical string, encoding, header, and timestamp rule, and the failure mode is silent: your check just returns false and you lose an afternoon. hooksig makes the whole thing visible, then hands you one typed API to verify any of them.

The three parts

hooksig/
├─ packages/sdk            hooksig, zero-dep WebCrypto verifier (Workers, Bun, Deno, Node)
├─ packages/test-vectors   shared signed vectors; the SDK and the Rust core both verify against them
├─ crates/hooksig-core  Rust canonical-string + HMAC + step output, compiled to WASM for the playground
└─ apps/web               the reference site + interactive playground (Astro + React island)

The playground is the hero: it becomes the page people link to. The SDK rides underneath as the "or just drop this in" conversion.

Verify a webhook in one line

import { verify } from "hooksig";

const result = await verify("stripe", {
  payload: rawBody,           // the RAW request body, never parsed-then-re-stringified
  headers: request.headers,   // Fetch Headers, a plain object, or Node headers
  secret: env.STRIPE_WEBHOOK_SECRET,
});

if (!result.verified) {
  return new Response(`rejected: ${result.reason}`, { status: 400 });
}

One API across stripe, github, and standardwebhooks (which covers OpenAI, Anthropic, Supabase, Clerk, and Resend). No dependencies, no Node built-ins, so it runs unchanged on Cloudflare Workers where the official Node SDKs cannot.

Status

  • SDK: working and tested. Ten providers. Builds (ESM + CJS + d.ts), typechecks clean, 53 tests over valid, tampered, stale-timestamp, multi-signature, wrong-url, and RSA vectors. Run cd packages/sdk && node --test test/*.test.ts.
  • Responsive + accessible. Layout is rem-based so text scales with the browser font-size setting; verified no horizontal overflow across 320 to 1280px and at a 22px base font. pnpm --filter @hooksig/web verify:ui drives all pages in a real headless browser.
  • Rust core: built and verified. cargo test passes the shared-vector suite with verdicts identical to the SDK, and wasm-pack build --target web produces the WASM module that computes the same signatures byte for byte. The two implementations cannot drift. (PayPal's RSA is SDK-only; the WASM core is HMAC-only for now.)
  • Playground: scaffolded, builds, and functional. Astro prerenders one SEO page per provider; the React island runs on the SDK's explain() today, and the WASM core swaps in behind the same call with no UI change.

Develop

The SDK needs only Node and pnpm. The web app's playground runs on the WASM core, so building or serving it also needs the Rust toolchain and wasm-pack (cargo install wasm-pack). The playground falls back to the pure-TS SDK at runtime if the WASM ever fails to load.

pnpm install
pnpm --filter hooksig test    # run the verifier tests (no Rust needed)
pnpm --filter @hooksig/web dev            # builds the WASM core, then serves the site
pnpm vectors                                   # regenerate the shared test vectors

Provider roadmap

All nineteen shipping: Stripe, GitHub, Standard Webhooks, Shopify, Slack, Square, Twilio, PayPal, Cloudflare, Vercel, Razorpay, Cashfree, Notion, Linear, Sentry, Zoom, Calendly, DocuSign, and MongoDB Atlas. Twilio, Vercel, and MongoDB Atlas use HMAC-SHA1 (Atlas signs the raw body, base64, in an X-MMS-Signature header). Razorpay and Cashfree are the Indian gateways: Razorpay signs the raw body like GitHub, Cashfree folds the timestamp in front of the body before hashing. Notion, Linear, and Sentry are GitHub-shaped (raw body, hex): Notion keeps the sha256= prefix, Linear and Sentry ship a bare digest. Zoom mirrors Slack, Calendly mirrors Stripe, and DocuSign mirrors Shopify (base64) but strips double-quotes from the HMAC secret and rotates keys across numbered headers. PayPal is asymmetric (RSA over transmission headers plus a CRC32 of the body) and is verified in the SDK only, since the WASM playground core is HMAC-only for now; its playground page runs on the SDK path. Each HMAC provider is a data-and-adapter add on a proven format.

Principles

  1. Correctness is existential. A verifier that ever accepts a forged signature is worse than nothing, so every provider ships with test vectors before it ships.
  2. Constant-time comparison, always. No early-out on the first differing byte.
  3. Sign the raw bytes. The SDK never re-serializes your body, and the docs hammer this because it is the number one cause of false failures.

MIT licensed.

About

The jwt.io of webhooks — a live playground + zero-dependency SDK to verify signatures from 10 providers (Stripe, GitHub, Svix, Shopify, Slack, Square, Twilio, PayPal, Cloudflare, Vercel).

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages