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

@bernierllc/webhook-sender

v0.1.5

Published

HTTP webhook delivery with retry logic, signature generation, and delivery tracking

Readme

@bernierllc/webhook-sender

HTTP webhook delivery with retry logic, signature generation, and delivery tracking.

Overview

Reliable webhook delivery system with exponential backoff, signature generation, timeout handling, and delivery status tracking. Framework-agnostic core that handles the complexity of reliable webhook delivery.

Features

  • Reliable Delivery: Exponential backoff retry with configurable policies
  • Signature Generation: HMAC-SHA256, JWT, custom signature algorithms
  • Timeout Handling: Configurable request and delivery timeouts
  • Delivery Tracking: Track delivery status, attempts, and response codes
  • Batch Delivery: Send multiple webhooks efficiently
  • Circuit Breaker: Disable failing endpoints temporarily
  • Rate Limiting: Control delivery rate for batch operations
  • NeverHub Integration: Optional event publishing for monitoring

Installation

npm install @bernierllc/webhook-sender

Quick Start

Basic Webhook Sending

import { WebhookSender } from '@bernierllc/webhook-sender';

const sender = new WebhookSender({
  timeout: 30000,
  retryPolicy: {
    maxAttempts: 3,
    initialDelayMs: 1000,
    maxDelayMs: 10000,
    backoffMultiplier: 2,
    jitter: true,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504]
  }
});

const result = await sender.send({
  url: 'https://api.example.com/webhooks',
  body: {
    event: 'user.created',
    data: { userId: '123', email: '[email protected]' }
  },
  signature: {
    algorithm: 'hmac-sha256',
    secret: process.env.WEBHOOK_SECRET!,
    header: 'x-signature-256'
  },
  metadata: {
    source: 'user-service',
    event: 'user.created'
  }
});

if (result.success) {
  console.log(`Webhook delivered in ${result.duration}ms after ${result.attempts} attempts`);
} else {
  console.error(`Webhook failed: ${result.error}`);
}

Batch Webhook Delivery

import { WebhookSender } from '@bernierllc/webhook-sender';

const sender = new WebhookSender();

const webhooks = [
  {
    url: 'https://api.example.com/webhooks',
    body: { event: 'order.created', orderId: '123' }
  },
  {
    url: 'https://partner.example.com/notify',
    body: { event: 'order.created', orderId: '123' }
  }
];

const results = await sender.sendBatch(webhooks);

results.forEach((result, index) => {
  if (result.success) {
    console.log(`Webhook ${index} delivered successfully`);
  } else {
    console.error(`Webhook ${index} failed: ${result.error}`);
  }
});

GitHub-Style Webhooks

import { WebhookSender, generateHmacSignature } from '@bernierllc/webhook-sender';

const sender = new WebhookSender({
  retryPolicy: {
    maxAttempts: 3,
    initialDelayMs: 1000,
    maxDelayMs: 30000,
    backoffMultiplier: 2,
    jitter: true,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504]
  }
});

await sender.send({
  url: webhookUrl,
  headers: {
    'content-type': 'application/json',
    'user-agent': 'MyApp-Hookshot/1.0',
    'x-github-event': 'push',
    'x-github-delivery': generateWebhookId()
  },
  body: pushEventData,
  signature: {
    algorithm: 'hmac-sha256',
    secret: webhookSecret,
    header: 'x-hub-signature-256'
  }
});

API Reference

WebhookSender

Main class for sending webhooks.

Constructor

new WebhookSender(options?: DeliveryOptions)

Methods

  • send(payload: WebhookPayload): Promise<DeliveryResult> - Send a single webhook
  • sendBatch(payloads: WebhookPayload[]): Promise<DeliveryResult[]> - Send multiple webhooks
  • generateSignature(payload: string, secret: string, algorithm: string): string - Generate signature
  • validateDelivery(result: DeliveryResult): boolean - Validate delivery result
  • getDeliveryStats() - Get delivery statistics
  • resetCircuitBreaker() - Reset circuit breaker state

WebhookPayload

interface WebhookPayload {
  url: string;                    // Webhook endpoint URL
  method?: string;                // HTTP method (default: POST)
  headers?: Record<string, string>; // Custom headers
  body: any;                      // Request body
  signature?: {                    // Optional signature configuration
    algorithm: 'hmac-sha256' | 'hmac-sha1' | 'jwt' | 'custom';
    secret: string;
    header?: string;
  };
  metadata?: {                     // Optional metadata
    id?: string;
    source?: string;
    event?: string;
    timestamp?: string;
  };
}

DeliveryOptions

interface DeliveryOptions {
  timeout?: number;                // Request timeout (default: 30s)
  retryPolicy?: RetryPolicy;       // Retry configuration
  circuitBreaker?: CircuitBreakerOptions; // Circuit breaker config
  validateResponse?: (response: Response) => boolean; // Custom response validation
  transformPayload?: (payload: any) => any; // Payload transformation
}

RetryPolicy

interface RetryPolicy {
  maxAttempts: number;             // Maximum retry attempts
  initialDelayMs: number;          // Initial delay between retries
  maxDelayMs: number;              // Maximum delay between retries
  backoffMultiplier: number;       // Exponential backoff multiplier
  jitter: boolean;                 // Add random jitter to delays
  retryableStatusCodes: number[];  // HTTP status codes to retry
}

CircuitBreakerOptions

interface CircuitBreakerOptions {
  failureThreshold: number;        // Failures before opening circuit
  recoveryTimeout: number;         // Time to wait before recovery
  monitorInterval: number;         // Circuit state check interval
}

Configuration

Default Options

import { DEFAULT_OPTIONS } from '@bernierllc/webhook-sender';

// Default configuration
const defaultConfig = {
  timeout: 30000, // 30 seconds
  retryPolicy: {
    maxAttempts: 3,
    initialDelayMs: 1000,
    maxDelayMs: 10000,
    backoffMultiplier: 2,
    jitter: true,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504]
  },
  circuitBreaker: {
    failureThreshold: 5,
    recoveryTimeout: 60000, // 1 minute
    monitorInterval: 10000  // 10 seconds
  }
};

Custom Response Validation

const sender = new WebhookSender({
  validateResponse: (response) => {
    // Custom validation logic
    return response.status >= 200 && response.status < 300 && 
           response.headers.get('x-custom-header') === 'expected-value';
  }
});

Payload Transformation

const sender = new WebhookSender({
  transformPayload: (payload) => {
    // Transform payload before sending
    return {
      ...payload,
      timestamp: new Date().toISOString(),
      version: '1.0'
    };
  }
});

Signature Generation

HMAC Signatures

import { generateHmacSignature } from '@bernierllc/webhook-sender';

const signature = generateHmacSignature(
  JSON.stringify(payload),
  secret,
  'sha256'
);

JWT Signatures

import { generateJwtSignature } from '@bernierllc/webhook-sender';

const signature = generateJwtSignature(
  payload,
  secret,
  { expiresIn: '1h', issuer: 'my-app' }
);

Custom Signatures

import { generateCustomSignature } from '@bernierllc/webhook-sender';

const signature = generateCustomSignature(
  JSON.stringify(payload),
  secret,
  (payload, secret) => {
    // Custom signature generation logic
    return customHashFunction(payload + secret);
  }
);

Batch Delivery

Concurrency Control

const batchSender = new BatchWebhookSender(sender, {
  maxConcurrency: 10,
  rateLimit: {
    requestsPerSecond: 5,
    burstSize: 20
  }
});

const results = await batchSender.sendBatch(webhooks);

Batch Statistics

const stats = batchSender.getBatchStats(results);
console.log(`Success rate: ${stats.successRate}%`);
console.log(`Average delivery time: ${stats.averageDeliveryTime}ms`);

Delivery Tracking

Get Delivery Statistics

const stats = sender.getDeliveryStats();
console.log(`Total sent: ${stats.totalSent}`);
console.log(`Success rate: ${(stats.totalSuccessful / stats.totalSent) * 100}%`);
console.log(`Circuit breaker state: ${stats.circuitBreakerState}`);

Track Specific Deliveries

const delivery = sender.getDelivery(webhookId);
if (delivery) {
  console.log(`Delivery ${webhookId}: ${delivery.success ? 'Success' : 'Failed'}`);
}

NeverHub Integration

Initialize Integration

import { initializeNeverHubIntegration } from '@bernierllc/webhook-sender';

await initializeNeverHubIntegration();

Publish Events

import { publishDeliverySuccess, publishDeliveryFailure } from '@bernierllc/webhook-sender';

const result = await sender.send(webhook);

if (result.success) {
  await publishDeliverySuccess(result);
} else {
  await publishDeliveryFailure(result);
}

Error Handling

WebhookError

import { WebhookError } from '@bernierllc/webhook-sender';

try {
  const result = await sender.send(webhook);
  if (!result.success) {
    throw new WebhookError(
      result.error || 'Delivery failed',
      'DELIVERY_FAILED',
      result.statusCode,
      result.attempts
    );
  }
} catch (error) {
  if (error instanceof WebhookError) {
    console.error(`Webhook delivery failed after ${error.attempts} attempts`);
  }
}

Error Codes

import { ERROR_CODES } from '@bernierllc/webhook-sender';

// Available error codes
const codes = {
  DELIVERY_FAILED: 'DELIVERY_FAILED',
  TIMEOUT: 'TIMEOUT',
  CIRCUIT_OPEN: 'CIRCUIT_OPEN',
  INVALID_URL: 'INVALID_URL',
  SIGNATURE_GENERATION_FAILED: 'SIGNATURE_GENERATION_FAILED',
  RATE_LIMITED: 'RATE_LIMITED',
  NETWORK_ERROR: 'NETWORK_ERROR'
};

Performance

Batch Processing

  • Concurrency: Control parallel webhook delivery
  • Rate Limiting: Respect API rate limits
  • Connection Reuse: Efficient HTTP connection handling
  • Memory Management: Minimal memory footprint

Monitoring

  • Delivery Metrics: Track success/failure rates
  • Performance Metrics: Monitor delivery times
  • Circuit Breaker: Automatic failure detection
  • Retry Analytics: Understand retry patterns

Testing

Mock Responses

// Mock fetch for testing
global.fetch = jest.fn(() =>
  Promise.resolve({
    ok: true,
    status: 200,
    text: () => Promise.resolve('OK'),
    url: 'https://example.com/webhook'
  } as Response)
);

Test Delivery Results

const result = await sender.send(webhook);

expect(result.success).toBe(true);
expect(result.statusCode).toBe(200);
expect(result.attempts).toBe(1);
expect(result.duration).toBeGreaterThan(0);

Dependencies

  • @bernierllc/retry-policy - Retry logic and policies
  • @bernierllc/retry-state - Retry state management
  • @bernierllc/retry-metrics - Retry metrics collection
  • @bernierllc/crypto-utils - Cryptographic utilities
  • @bernierllc/logger - Logging functionality
  • @bernierllc/neverhub-adapter - NeverHub integration (optional)

License

Copyright (c) 2025 Bernier LLC

This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.