Docs
/
HMAC webhook signing & verification

webhook-forge

Webhook signing + verification adapter pack — generic HMAC (sha256/384/512, hex/base64) with a timing-safe compare, Stripe's t=/v1= scheme with timestamp tolerance, GitHub's X-Hub-Signature-256, a...

webhook-forge — HMAC webhook signing & verification

Sign outgoing webhooks and verify incoming ones with node:crypto HMAC, across the schemes real providers use (generic HMAC, Stripe, GitHub). Pure ESM, Node built-ins only, zero npm deps. Self-contained and deterministic — no network, no stored secrets, timing-safe comparisons.

Tools

ToolInputOutput
sign{ payload, secret, scheme?, encoding?, options?.header }Generic HMAC signature + ready-to-send header.
verify{ payload, secret, signature, scheme?, encoding? }{ valid, reason } via timing-safe compare (accepts a sha256= prefix).
stripeSignature{ payload, secret, timestamp? }Stripe-Signature: t=<ts>,v1=<hex> header.
stripeVerify{ payload, secret, signature, toleranceSeconds? }Recomputes v1, timing-safe compare and timestamp tolerance (anti-replay).
githubVerify{ payload, secret, signature }Verifies GitHub's sha256=<hex> X-Hub-Signature-256.
timestampToleranceCheck{ timestamp, toleranceSeconds? }Is a unix timestamp fresh (within ± tolerance of now)?
replayGuard{ id, options?.ttlSeconds }In-memory seen-id guard: first sight passes, repeats inside the TTL are rejected.

Usage

import pack from './index.js';

// outgoing
const s = pack.adapters.sign({ payload: '{"a":1}', secret: 'whsec_x', scheme: 'hmac-sha256' });
// s.header -> { name:'X-Signature', value:'<hex>' }

// incoming (Stripe)
const r = pack.adapters.stripeVerify({ payload: rawBody, secret: 'whsec_x', signature: header, toleranceSeconds: 300 });
// r.valid, r.reason, r.ageSeconds

Important: for verification, pass the exact raw body string you received. Passing an object re-stringifies it and can reorder keys, breaking the signature — the tools warn via warnObjectPayload.

AI mode

Verification is always decided deterministically. With options.ai: true and a reachable model, a failed verify additionally returns an aiHint explaining the likely cause (the promise resolves to the same result object with the hint attached). A model never changes the valid/invalid verdict.

DRY boundary

HMAC signing/verification of webhook payloads. Not general hashing/encoding (see crypto) and it makes no HTTP requests. Dockerfile tooling lives in dockerfile; dependency auditing in dependency-auditor.

Source shared/engines/adapters/domain/webhook-forge/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt