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

kxco-post-quantum-webhook

v1.0.6

Published

Post-quantum ML-DSA-65 webhook signing and verification. Sign outgoing payloads so recipients can prove they came from you; verify incoming webhooks before processing them. Drop-in for HMAC-SHA256 webhook patterns, but quantum-safe.

Readme

kxco-post-quantum-webhook

npm Socket license node

Post-quantum ML-DSA-65 webhook signing and verification. Sign outgoing webhook payloads so recipients can prove they came from you. Verify incoming webhooks before processing them. Drop-in replacement for HMAC-SHA256 webhook patterns, but quantum-safe.


When to use this

Use this package when you need proof that a webhook delivery came from a specific sender — not just that the payload was not tampered with in transit.

HMAC-SHA256 is a shared secret: the sender and receiver both hold the key, so either party could have produced the signature. ML-DSA-65 is an asymmetric signature scheme: only the sender holds the private key, and anyone holding the corresponding public key can verify. That property is called non-repudiation, and it matters when:

  • You are receiving webhooks from a partner and need to be certain they originated from that partner's infrastructure, not a replay or a man-in-the-middle.
  • You are sending webhooks to customers who need to prove, to a third party, that a specific event was delivered by your platform and not fabricated by them.
  • You are replacing HMAC-SHA256 webhook patterns with something that holds up against quantum computers.

This package sends both HMAC-SHA256 and ML-DSA-65 signatures by default. Receivers can require either or both. During migration from HMAC-only setups, the required: 'either' policy lets receivers accept both old and new deliveries.


Install

npm install kxco-post-quantum-webhook kxco-post-quantum

kxco-post-quantum is a peer dependency. Your application supplies the version.


Quick start

Sign an outgoing webhook

import { mlDsa, fingerprint }                from 'kxco-post-quantum'
import { createSigner, signedFetch }         from 'kxco-post-quantum-webhook'

const kp     = mlDsa.keypairFromMaster(process.env.KEY_MASTER, 'my-app-v1')
const kid    = fingerprint(kp.publicKey)

const signer = createSigner({
  hmacSecret:  process.env.WEBHOOK_HMAC_SECRET,
  pqSecretKey: kp.secretKey,
  pqKid:       kid,
})

await signedFetch('https://receiver.example.com/webhooks/incoming', {
  signer,
  body:  { event: 'invoice.paid', amount: 12500 },
  event: 'invoice.paid',
})

Verify an incoming webhook

import { createVerifier } from 'kxco-post-quantum-webhook'

const verifier = createVerifier({
  hmacSecret:  process.env.WEBHOOK_HMAC_SECRET,
  pqPublicKey: process.env.SENDER_PQ_PUBKEY_HEX,
  pinnedKid:   process.env.SENDER_PQ_KID,
  required:    'both',
})

// In your request handler — rawBody must be the exact bytes received
const result = verifier.verify(req.headers, rawBody)

if (!result.ok) {
  // result.reason is one of: timestamp_skew | kid_mismatch |
  // missing_hmac | missing_pq | hmac_invalid | pq_invalid
  return res.status(401).json({ error: result.reason })
}

// Signature is valid — safe to process

Framework adapters (Express, Fastify, Hono, Cloudflare Workers, Vercel) handle raw-body capture and the 401 response automatically. See the per-framework examples below.


Framework adapters

Pick the adapter that matches your stack.

Express

import express                            from 'express'
import { createVerifier }                 from 'kxco-post-quantum-webhook'
import { pqWebhook }                      from 'kxco-post-quantum-webhook/express'

const verifier = createVerifier({
  hmacSecret:  process.env.WEBHOOK_HMAC_SECRET,
  pqPublicKey: process.env.SENDER_PQ_PUBKEY_HEX,
  pinnedKid:   process.env.SENDER_PQ_KID,
  required:    'both',
})

const app = express()

app.post('/webhooks/incoming',
  express.raw({ type: '*/*' }),
  pqWebhook(verifier),
  (req, res) => {
    const event = JSON.parse(req.body.toString('utf-8'))
    res.json({ ok: true })
  },
)

express.raw() is required. The signature is over the exact body bytes — if Express parses the body first, verification will fail.

Fastify

import Fastify                            from 'fastify'
import { createVerifier }                 from 'kxco-post-quantum-webhook'
import pqWebhookPlugin                    from 'kxco-post-quantum-webhook/fastify'

const app = Fastify()
await app.register(pqWebhookPlugin, { verifier })

app.post('/webhooks/incoming', async (req) => ({
  ok:   req.kxcoWebhook.ok,
  body: JSON.parse(req.body.toString('utf-8')),
}))

Hono

import { Hono }                           from 'hono'
import { createVerifier }                 from 'kxco-post-quantum-webhook'
import { pqWebhook }                      from 'kxco-post-quantum-webhook/hono'

const app = new Hono()
app.use('/webhooks/incoming', pqWebhook(verifier))
app.post('/webhooks/incoming', async (c) => c.json({ ok: true }))

Cloudflare Workers

import { createVerifier }                 from 'kxco-post-quantum-webhook'
import { withPqWebhook }                  from 'kxco-post-quantum-webhook/workers'

export default {
  fetch: withPqWebhook(verifier, async (req, env, ctx, result) => {
    const event = JSON.parse(await req.text())
    return new Response(JSON.stringify({ ok: true }))
  }),
}

Vercel Functions (Node runtime)

import { createVerifier }                 from 'kxco-post-quantum-webhook'
import { nodePqWebhook }                  from 'kxco-post-quantum-webhook/vercel'

export const config = { api: { bodyParser: false } }

export default nodePqWebhook(verifier, async (req, res) => {
  const event = JSON.parse(req.rawBody.toString('utf-8'))
  res.status(200).json({ ok: true })
})

For the Vercel Edge runtime, use the Workers adapter (withPqWebhook).


The required policy

createVerifier({ required }) controls what counts as a passing verification:

| required | Passes when | |-------------|---------------------------------------------------| | 'both' | Both HMAC and ML-DSA-65 signatures are valid — default; recommended for production | | 'pq' | ML-DSA-65 signature is valid | | 'hmac' | HMAC-SHA256 signature is valid | | 'either' | Either signature passes — useful during migration from HMAC-only |

When ok is false, result.reason contains one of: timestamp_skew, kid_mismatch, missing_hmac, missing_pq, hmac_invalid, pq_invalid.


Wire format

The signature envelope is ${timestamp}.${rawBody}. Headers sent with every delivery:

| Header | Description | |-----------------------|--------------------------------------------------| | X-KXCO-Timestamp | Unix seconds | | X-KXCO-Signature | sha256=<64 hex chars> HMAC-SHA256 | | X-KXCO-PQ-Signature | ml-dsa-65=<hex> ML-DSA-65 signature | | X-KXCO-PQ-Kid | 16 hex chars — SHA-256 of the public key bytes, first 8 bytes | | X-KXCO-Event | Optional event name | | X-KXCO-Delivery | Optional idempotency / trace ID |

The full wire-format spec is in docs/webhook-contract.md. It is language-neutral — anyone can re-implement the verifier in Rust, Go, Python, or any other language against the canonical math.


Key rotation

When rotating signing keys, a verifier can accept multiple kids during the drain window — in-flight deliveries signed by the old key continue to verify until they expire.

const verifier = createVerifier({
  pinnedKids: [
    { kid: '<new-kid>', publicKey: '<new-pubkey-hex>' },   // active
    { kid: '<old-kid>', publicKey: '<old-pubkey-hex>' }    // retiring
  ],
  required: 'pq',
})

const result = verifier.verify(req.headers, req.body)
// result.resolvedKid — which key was used for this delivery

pinnedKid (singular) continues to work unchanged and is mutually exclusive with pinnedKids.


API

All exports from the main entry point (kxco-post-quantum-webhook):

createSigner(opts)Signer

Builds a reusable signing object. At least one of hmacSecret or pqSecretKey is required.

opts:
  hmacSecret   string | Buffer        — shared HMAC-SHA256 secret
  pqSecretKey  Buffer | Uint8Array    — ML-DSA-65 secret key (4032 bytes)
  pqKid        string                 — fingerprint of the matching public key; required when pqSecretKey is set

Returns:
  signer.sign(rawBody, { event?, deliveryId? }) → Record<string, string>
  signer.pqKid  string | undefined

createVerifier(opts)Verifier

Builds a reusable verifier. At least one of hmacSecret, pqPublicKey, or pinnedKids is required.

opts:
  hmacSecret     string | Buffer               — shared HMAC-SHA256 secret
  pqPublicKey    string | Buffer | Uint8Array  — ML-DSA-65 public key (1952 bytes or hex string)
  pinnedKid      string                        — required when pqPublicKey is set
  pinnedKids     Array<{ kid, publicKey }>     — multi-key form for rotation; mutually exclusive with pinnedKid/pqPublicKey
  windowSeconds  number                        — max clock skew in seconds (default: 300)
  required       'both' | 'pq' | 'hmac' | 'either'  — verification policy (default: 'both')

Returns:
  verifier.verify(headers, rawBody) → VerifyResult
  verifier.required  string

VerifyResult:
  ok            boolean   — overall verdict
  hmacOk        boolean   — HMAC check passed
  pqOk          boolean   — ML-DSA-65 check passed
  timestampOk   boolean   — timestamp within windowSeconds
  kidOk         boolean   — kid header matched pinnedKid
  reason        string?   — when !ok: timestamp_skew | kid_mismatch | missing_hmac | missing_pq | hmac_invalid | pq_invalid
  resolvedKid   string?   — when pinnedKids[] matched: which kid was used

signedFetch(url, opts)Promise<Response>

Signs and POSTs a body in one call. Returns the raw fetch Response — does not throw on non-2xx status codes.

url   string                    — absolute http(s) URL
opts:
  signer      Signer            — from createSigner()
  body        any               — JSON-stringified if not already a string or Buffer
  event       string?           — sets X-KXCO-Event header
  deliveryId  string?           — sets X-KXCO-Delivery header
  headers     Record<string, string>?  — merged after signing; signing headers take precedence
  method      string?           — default: 'POST'
  fetchImpl   function?         — custom fetch implementation; defaults to globalThis.fetch

signedEnvelope(signer, body, opts?){ rawBody, headers }

Lower-level helper. Returns the signed headers and canonical body without making a request. Use when you already have your own HTTP client.

signResponse(signer, body, opts?)Record<string, string>

Computes signing headers for an outgoing API response body. Same wire format as signer.sign(). Used internally by the response-signing middleware in each framework adapter. Import from kxco-post-quantum-webhook/response-core.

isStreamingBody(body)boolean

Returns true if body is a Node.js Readable stream or a Web ReadableStream. Response-signing middleware uses this to skip signing on streaming routes. Import from kxco-post-quantum-webhook/response-core.

verifiedFetch(url, init, opts)Promise<{ response, kxcoResponse }>

Fetch-and-verify in one call. Buffers the response body, runs the verifier, then returns a re-wrapped Response that can still be .json()-ed or .text()-ed. Import from kxco-post-quantum-webhook/verified-fetch.

Throws KxcoResponseError before the caller can read the body when the signature fails. Pass permissive: true to return the result even on failure.

opts:
  verifier    Verifier      — from createVerifier()
  permissive  boolean?      — if true, return result even when !ok instead of throwing
  fetchImpl   function?     — custom fetch implementation

KxcoResponseError

Thrown by verifiedFetch on signature failure. Import from kxco-post-quantum-webhook/verified-fetch.

err.kxcoResponse   VerifyResult   — the full verification result
err.response       Response       — the unverified response (buffered body)
err.code           string         — 'kxco_response_unverified'

webhook

Re-export of the low-level webhook namespace from kxco-post-quantum. Use this if you want to drop below the opinionated builders and call signDelivery / verifyDelivery directly.


Response signing

The same wire format applies to outbound API responses. Mount the response-signing middleware on specific routes so recipients can verify API responses with the same verifier they use for webhooks.

// Express — opt-in per route
import { createSigner }                   from 'kxco-post-quantum-webhook'
import { pqResponseSigner }               from 'kxco-post-quantum-webhook/express'

const signer = createSigner({ pqSecretKey: kp.secretKey, pqKid: kid })

app.post('/api/order',
  pqResponseSigner({ signer }),
  (req, res) => res.json({ orderId: 'ord_123' }),
)

The same pattern is available for Fastify (pqResponseSignerPlugin), Hono (pqResponseSigner), Cloudflare Workers (withPqResponseSigning), and Vercel Node Functions (pqResponseSigner).

Do not mount response-signing middleware on streaming routes (SSE, chunked transfer). The middleware buffers the full body to compute the signature envelope.


What this does NOT do

Payload encryption — signatures prove origin and integrity; they do not hide the content. For encrypted payloads, use kxco-pq-vault.

Identity credentials — this package does not issue, verify, or manage identity documents. For KYC-backed identity credentials tied to ML-DSA-65 keys, use kxco-pq-sdk.

JWT signing or JWKS endpoints — JWT envelopes have different semantics (JOSE alg fields, header/payload separation, JWKS discovery). ML-DSA-65 signatures at ~3 KB also exceed browser cookie limits, making JWT use impractical. Not in scope.

Generic HTTP security — CSRF, rate limiting, mTLS, and other HTTP-security concerns belong in your existing stack. This package does one thing: sign and verify webhook and API response payloads.


Part of the KXCO stack

| Package | What it does | |---------|--------------| | kxco-post-quantum | ML-DSA-65 + ML-KEM-768 primitives; keypair derivation; fingerprinting | | kxco-post-quantum-webhook | Webhook and API response signing + verification (this package) | | kxco-pq-cli | kxco-pq binary for keygen, fingerprint, and signed rotation manifests | | kxco-verify | Browser-safe verifier for deploy attestations and webhook deliveries |


Compatibility

  • Node.js >= 18 (uses native crypto.subtle via @noble/post-quantum)
  • Cloudflare Workers, Deno Deploy, Vercel Edge, Bun
  • Any modern browser via a bundler (Express and Fastify adapters are server-only)

Security

All signing and verification delegates to kxco-post-quantum, which wraps @noble/post-quantum — audited by Cure53 (2024). HMAC-SHA256 uses the Node.js built-in crypto module. No outbound network calls are made; this is a pure signing and verification layer.

Keep private keys in environment variables or a KMS. Never log pqSecretKey or hmacSecret. Use required: 'both' in production unless you have a documented reason not to.

To report a vulnerability, open a private security advisory or email [email protected].


License

Apache 2.0 — see LICENSE. The upstream signer (kxco-post-quantum) is MIT. The split is deliberate — receivers of this package can audit the verification path in isolation.


Maintainers

Shayne Heffernan and John Heffernan — KXCO by Knightsbridge

Deployed in production at target150.com, knightsbridgelaw.com, livetradingnews.com.