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 🙏

© 2025 – Pkg Stats / Ryan Hefner

webhook-verify-all

v1.0.3

Published

Unified webhook signature verification for 12+ providers (Stripe, Slack, Shopify, PayPal, Telegram, Discord, GitHub, GitLab, Twilio, SendGrid, Auth0, Unlimit) with TypeScript support and optional replay protection

Readme

webhook-verify-all

npm version npm downloads License: MIT TypeScript

A professional, TypeScript-first npm package for verifying webhook signatures from major providers with a unified API and optional replay protection.

Features

  • TypeScript-first - Full type safety with strict mode
  • Multiple Providers - Stripe, Slack, Shopify, PayPal, Telegram, Discord, Unlimit, GitHub, GitLab, Twilio, SendGrid, Auth0
  • Unified API - Consistent interface across all providers
  • Replay Protection - Optional in-memory or Redis-based replay store
  • Framework Support - Express and Fastify middleware helpers
  • Security Best Practices - Constant-time comparison, timestamp validation, clock skew handling
  • Zero External Crypto Dependencies - Uses Node.js native crypto only
  • Production Ready - Comprehensive error handling, unit tests, and documentation

Why webhook-verify-all?

  • 🚀 One package for all providers - No need to install separate packages for each webhook provider
  • 🔒 Security-first - Constant-time comparison, replay protection, and timestamp validation built-in
  • 📦 Zero dependencies - Uses only Node.js native crypto (except optional ioredis for Redis)
  • 🎯 Type-safe - Full TypeScript support with strict mode
  • Framework agnostic - Works with Express, Fastify, or any Node.js framework
  • 🛡️ Production tested - 109+ unit and integration tests

Installation

npm install webhook-verify-all

For Express or Fastify middleware, install Express or Fastify in your app:

# For Express
npm install webhook-verify-all express

# For Fastify
npm install webhook-verify-all fastify

For Redis replay protection:

npm install webhook-verify-all ioredis

Quick Start

Basic Usage

import { verifyWebhook } from "webhook-verify-all";

const result = await verifyWebhook("stripe", {
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
  body: rawRequestBody,
  signature: req.headers["stripe-signature"],
});

if (result.ok) {
  // Webhook is verified, process it
  console.log(`Verified ${result.provider} webhook`);
} else {
  // Verification failed
  console.error(`Error: ${result.error} (${result.errorCode})`);
}

Note: The unified API (verifyWebhook) is the recommended approach. Provider-specific verifiers (e.g., verifyStripe) are also available for convenience.

Express Middleware

import express from "express";
import { webhookMiddleware } from "webhook-verify-all/express";

const app = express();

// Important: Parse raw body BEFORE the webhook middleware
// Do NOT use express.json() on the same route - use express.raw() instead
app.use("/webhook", express.raw({ type: "application/json" }));

app.post(
  "/webhook",
  webhookMiddleware({
    provider: "stripe",
    secret: process.env.STRIPE_WEBHOOK_SECRET!,
  }),
  (req, res) => {
    if (req.webhook?.ok) {
      // Handle verified webhook
      res.json({ received: true });
    } else {
      res.status(401).json({ error: req.webhook?.error });
    }
  }
);

Fastify Middleware

import Fastify from "fastify";
import { webhookMiddleware } from "webhook-verify-all/fastify";

const fastify = Fastify();

fastify.post(
  "/webhook",
  {
    preHandler: webhookMiddleware({
      provider: "stripe",
      secret: process.env.STRIPE_WEBHOOK_SECRET!,
    }),
  },
  async (req, reply) => {
    if (req.webhook?.ok) {
      return { received: true };
    } else {
      reply.code(401);
      return { error: req.webhook?.error };
    }
  }
);

Supported Providers

Stripe

import { verifyStripe } from "webhook-verify-all";

const result = await verifyStripe({
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["stripe-signature"]!,
  timestampTolerance: 300, // 5 minutes
});

Headers Required:

  • stripe-signature: Format t=timestamp,v1=signature

Slack

import { verifySlack } from "webhook-verify-all";

const result = await verifySlack({
  secret: process.env.SLACK_SIGNING_SECRET!,
  body: rawBody,
  signature: req.headers["x-slack-signature"]!,
  timestamp: req.headers["x-slack-request-timestamp"]!,
});

Headers Required:

  • x-slack-signature: Format v0=signature
  • x-slack-request-timestamp: Unix timestamp

Shopify

import { verifyShopify } from "webhook-verify-all";

const result = await verifyShopify({
  secret: process.env.SHOPIFY_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["x-shopify-hmac-sha256"]!,
});

Headers Required:

  • x-shopify-hmac-sha256: HMAC-SHA256 signature (base64 encoded)

PayPal

import { verifyPayPal } from "webhook-verify-all";

const result = await verifyPayPal({
  secret: process.env.PAYPAL_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["paypal-transmission-sig"]!,
});

Headers Required:

  • paypal-transmission-sig: Signature header

Note: PayPal uses certificate/public-key verification (not simple HMAC). This implementation provides a simplified HMAC-based approach. For production use, consider PayPal's official SDK for full certificate verification with PayPal-Transmission-Id, PayPal-Transmission-Time, PayPal-Cert-Url, and PayPal-Auth-Algo headers.

Telegram

import { verifyTelegram } from "webhook-verify-all";

const result = await verifyTelegram({
  secret: process.env.TELEGRAM_BOT_SECRET!,
  body: rawBody,
  signature: req.headers["x-telegram-bot-api-secret-token"]!,
});

Headers Required:

  • x-telegram-bot-api-secret-token: Secret token (must match the secret you set via setWebhook)

Note: Telegram uses simple token equality check (constant-time comparison), not HMAC. The token in the header must exactly match the secret you configured when setting up the webhook.

Discord

import { verifyDiscord } from "webhook-verify-all";

const result = await verifyDiscord({
  secret: process.env.DISCORD_PUBLIC_KEY!, // Hex-encoded Ed25519 public key
  body: rawBody,
  signature: req.headers["x-signature-ed25519"]!,
  headers: {
    "x-signature-timestamp": req.headers["x-signature-timestamp"]!,
  },
});

Headers Required:

  • x-signature-ed25519: Hex-encoded Ed25519 signature
  • x-signature-timestamp: Unix timestamp

Note: Discord uses Ed25519 signatures, not HMAC. The secret should be the hex-encoded public key.

Unlimit

import { verifyUnlimit } from "webhook-verify-all";

const result = await verifyUnlimit({
  secret: process.env.UNLIMIT_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["x-signature"]!,
});

Headers Required:

  • x-signature: HMAC-SHA256 signature

GitHub

import { verifyGitHub } from "webhook-verify-all";

const result = await verifyGitHub({
  secret: process.env.GITHUB_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["x-hub-signature-256"]!,
});

Headers Required:

  • x-hub-signature-256: Format sha256=signature

GitLab

import { verifyGitLab } from "webhook-verify-all";

const result = await verifyGitLab({
  secret: process.env.GITLAB_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["x-gitlab-token"]!,
});

Headers Required:

  • x-gitlab-token: Shared secret token (must match the secret you configured)

Note: GitLab uses simple token equality check (constant-time comparison), not HMAC. The token in the header must exactly match your configured webhook secret.

Twilio

import { verifyTwilio } from "webhook-verify-all";

const result = await verifyTwilio({
  secret: process.env.TWILIO_AUTH_TOKEN!,
  body: rawBody,
  signature: req.headers["x-twilio-signature"]!,
});

Headers Required:

  • x-twilio-signature: HMAC-SHA1 signature (base64 encoded)

Note: Twilio uses HMAC-SHA1, not SHA256.

SendGrid

import { verifySendGrid } from "webhook-verify-all";

const result = await verifySendGrid({
  secret: process.env.SENDGRID_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["x-sendgrid-signature"]!,
});

Headers Required:

  • x-sendgrid-signature: Signature header
  • Alternative: x-twilio-email-event-webhook-signature (legacy)

Note: SendGrid Event Webhook uses Ed25519 signatures with X-Twilio-Email-Event-Webhook-Signature and ...-Timestamp headers (not HMAC). This implementation provides a simplified HMAC-based approach. For production use, consider implementing Ed25519 verification with SendGrid's public key.

Auth0

import { verifyAuth0 } from "webhook-verify-all";

const result = await verifyAuth0({
  secret: process.env.AUTH0_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["auth0-signature"]!,
});

Headers Required:

  • auth0-signature: HMAC-SHA256 signature

Replay Protection

Prevent replay attacks by using a replay store:

In-Memory Store

import { verifyWebhook, InMemoryReplayStore } from "webhook-verify-all";

const replayStore = new InMemoryReplayStore({ maxSize: 10000 });

const result = await verifyWebhook("stripe", {
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["stripe-signature"]!,
  replayStore,
});

Redis Store

import Redis from "ioredis";
import { verifyWebhook, RedisReplayStore } from "webhook-verify-all";

const redis = new Redis(process.env.REDIS_URL);
const replayStore = new RedisReplayStore({ client: redis });

const result = await verifyWebhook("stripe", {
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
  body: rawBody,
  signature: req.headers["stripe-signature"]!,
  replayStore,
});

Configuration Options

VerificationOptions

interface VerificationOptions {
  secret: string; // Secret key for verification
  body: string | Buffer; // Raw request body (exact bytes)
  signature: string; // Signature header value
  timestamp?: string; // Optional timestamp header
  timestampTolerance?: number; // Tolerance in seconds (default: 300)
  clockSkew?: number; // Clock skew in seconds (default: 0)
  replayStore?: ReplayStore; // Optional replay protection
}

MiddlewareOptions

interface MiddlewareOptions {
  provider: WebhookProvider; // Provider to verify
  secret: string; // Secret key
  timestampTolerance?: number; // Optional timestamp tolerance
  clockSkew?: number; // Optional clock skew
  replayStore?: ReplayStore; // Optional replay store
  getRawBody?: (req: any) => string | Buffer; // Custom raw body extractor
}

Error Handling

The package provides detailed error classes:

import {
  WebhookVerificationError,
  MissingHeaderError,
  BadSignatureError,
  TimestampError,
  ReplayError,
} from "webhook-verify-all";

try {
  const result = await verifyWebhook("stripe", options);
  if (!result.ok) {
    // Handle verification failure
    console.error(result.errorCode, result.error);
  }
} catch (error) {
  if (error instanceof MissingHeaderError) {
    // Missing required header
  } else if (error instanceof BadSignatureError) {
    // Signature verification failed
  } else if (error instanceof TimestampError) {
    // Timestamp validation failed
  } else if (error instanceof ReplayError) {
    // Replay attack detected
  }
}

Error Codes

  • ERR_NO_HEADER - Required header is missing
  • ERR_BAD_SIG - Signature verification failed
  • ERR_TIMESTAMP - Timestamp validation failed (expired or invalid)
  • ERR_REPLAY - Replay attack detected (signature already used)
  • ERR_UNSUPPORTED - Provider is not supported
  • ERR_VERIFICATION_FAILED - General verification failure

Security Best Practices

✅ Do

  • Always use the exact raw body bytes received from the webhook
  • Store secrets securely (environment variables, secret managers)
  • Enable replay protection in production
  • Set appropriate timestamp tolerance (typically 5 minutes)
  • Use constant-time comparison (handled automatically)
  • Validate webhooks before processing business logic
  • Log verification failures for monitoring

❌ Don't

  • Don't parse the body as JSON before verification
  • Don't modify the raw body in any way
  • Don't use weak secrets or hardcode them
  • Don't disable timestamp validation
  • Don't ignore verification failures
  • Don't use string comparison for signatures (use this library)

Raw Body Handling

Express

import express from "express";

const app = express();

// Parse raw body for webhook routes
app.use("/webhook", express.raw({ type: "application/json" }));

// Other routes can use JSON parser
app.use(express.json());

Fastify

import Fastify from "fastify";

const fastify = Fastify();

// Use rawBody plugin or custom parser
fastify.addContentTypeParser(
  "application/json",
  { parseAs: "buffer" },
  (req, body, done) => {
    done(null, body);
  }
);

TypeScript Support

Full TypeScript support with strict mode:

import type {
  WebhookProvider,
  VerificationResult,
  VerificationOptions,
  ReplayStore,
} from "webhook-verify-all";

const provider: WebhookProvider = "stripe";
const result: VerificationResult = await verifyWebhook(provider, options);

Testing

Run tests with Vitest:

npm test

Watch mode:

npm run test:watch

Architecture

  • Provider Registry Pattern - No switch/if-else chains, uses a registry map
  • Pluggable Replay Store - Interface-based design for extensibility
  • Framework Agnostic Core - Core logic independent of Express/Fastify
  • Type-Safe - Full TypeScript with strict mode
  • Minimal Dependencies - Only ioredis for Redis support (optional)

Comparison with Other Packages

| Feature | webhook-verify-all | Others | |---------|-------------------|--------| | Multiple Providers | ✅ 12+ providers | ❌ Usually 1-2 providers | | TypeScript Support | ✅ Full strict mode | ⚠️ Partial or none | | Replay Protection | ✅ Built-in | ❌ Manual implementation | | Framework Support | ✅ Express & Fastify | ⚠️ Limited | | Zero Dependencies | ✅ Native crypto only | ⚠️ External crypto libs | | Unified API | ✅ Consistent interface | ❌ Different APIs per provider |

Contributing

Contributions are welcome! Please ensure:

  • All tests pass
  • TypeScript compiles without errors
  • Code follows existing patterns
  • New providers include tests

License

MIT

Security

If you discover a security vulnerability, please do not open a public issue. Instead, please email [email protected] with details.

Changelog

1.0.3

  • Removed GitHub repository links (private repository)
  • Updated package.json metadata (removed repository, bugs, homepage fields)
  • Simplified Contributing section

1.0.2

  • Added badges (npm version, downloads, license, TypeScript)
  • Added "Why webhook-verify-all?" section
  • Added comparison table with other packages
  • Improved package description for better SEO
  • Fixed repository URL format

1.0.1

  • Fixed repository URL format
  • Improved error handling for Discord Ed25519 verification
  • Performance improvements (removed unnecessary async operations)

1.0.0

  • Initial release
  • Support for 12 providers: Stripe, Slack, Shopify, PayPal, Telegram, Discord, GitHub, GitLab, Twilio, SendGrid, Auth0, Unlimit
  • In-memory and Redis replay protection
  • Express and Fastify middleware
  • Full TypeScript support
  • 109+ unit and integration tests