@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-senderQuick 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 webhooksendBatch(payloads: WebhookPayload[]): Promise<DeliveryResult[]>- Send multiple webhooksgenerateSignature(payload: string, secret: string, algorithm: string): string- Generate signaturevalidateDelivery(result: DeliveryResult): boolean- Validate delivery resultgetDeliveryStats()- Get delivery statisticsresetCircuitBreaker()- 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.
