hook-engine
Version:
Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.
926 lines (798 loc) • 29.1 kB
text/typescript
/**
* 05 - E-commerce Platform Example (Real-World Use Case)
*
* This example demonstrates a complete e-commerce platform that uses
* ALL hook-engine features to handle webhooks from multiple providers:
* - Stripe (payments)
* - Shopify (orders)
* - GitHub (deployments)
* - SendGrid (email events)
*
* Features demonstrated:
* ✅ Multiple webhook adapters
* ✅ Structured logging with JSON output
* ✅ Security & reliability features
* ✅ Performance monitoring
* ✅ Error handling & recovery
* ✅ Multi-tenant support
* ✅ CLI integration
*/
import express from 'express';
import {
receiveWebhook,
StructuredLogger,
adapters
} from '../src/index';
const app = express();
const PORT = process.env.PORT || 3003;
// ==========================================
// 1. STRUCTURED LOGGING SETUP
// ==========================================
const logger = new StructuredLogger({
level: 'info',
format: 'json',
outputs: [
{
type: 'console',
config: { colorize: true }
},
{
type: 'file',
config: {
filename: './logs/ecommerce-platform.log'
}
}
],
enableColors: true,
enableTimestamps: true,
enableStackTrace: true,
maxFileSize: 50 * 1024 * 1024, // 50MB
maxFiles: 10,
rotateDaily: true
});
// ==========================================
// 2. WEBHOOK CONFIGURATIONS
// ==========================================
const webhookConfigs = {
stripe: {
source: 'stripe',
secret: process.env.STRIPE_WEBHOOK_SECRET || 'whsec_test_secret'
},
shopify: {
source: 'shopify',
secret: process.env.SHOPIFY_WEBHOOK_SECRET || 'shopify_test_secret'
},
github: {
source: 'github',
secret: process.env.GITHUB_WEBHOOK_SECRET || 'github_test_secret'
},
sendgrid: {
source: 'sendgrid',
secret: process.env.SENDGRID_WEBHOOK_SECRET || 'sendgrid_test_secret'
}
};
// ==========================================
// 3. BUSINESS LOGIC SERVICES
// ==========================================
class EcommerceServices {
// Payment processing service
static async processPayment(event: any, logger: any) {
const requestId = `payment_${Date.now()}`;
logger.info('Processing payment event', {
operation: 'payment_processing',
eventType: event.type,
eventId: event.id,
requestId
});
switch (event.type) {
case 'invoice.payment_succeeded':
await this.handlePaymentSuccess(event, logger, requestId);
break;
case 'invoice.payment_failed':
await this.handlePaymentFailure(event, logger, requestId);
break;
case 'customer.subscription.created':
await this.handleNewSubscription(event, logger, requestId);
break;
case 'customer.subscription.deleted':
await this.handleCancelledSubscription(event, logger, requestId);
break;
default:
logger.info(`Unhandled payment event: ${event.type}`, {
operation: 'unhandled_payment_event',
eventType: event.type,
requestId
});
}
}
static async handlePaymentSuccess(event: any, logger: any, requestId: string) {
const customerId = event.payload?.customer;
const amount = event.payload?.amount_paid;
logger.info('Payment succeeded - processing fulfillment', {
operation: 'payment_success',
customerId,
amount,
requestId
});
// Simulate business logic
await new Promise(resolve => setTimeout(resolve, 200));
// Log business metrics
logger.performance({
level: 'info',
operation: 'payment_fulfillment',
duration: 200,
metrics: {
revenue: amount,
customerId
},
metadata: { requestId }
});
logger.info('Payment fulfillment completed', {
operation: 'payment_success_completed',
customerId,
requestId
});
}
static async handlePaymentFailure(event: any, logger: any, requestId: string) {
const customerId = event.payload?.customer;
const failureReason = event.payload?.last_payment_error?.message;
logger.warn('Payment failed - initiating recovery', {
operation: 'payment_failure',
customerId,
failureReason,
requestId
});
// Simulate retry logic
await new Promise(resolve => setTimeout(resolve, 100));
logger.info('Payment failure recovery initiated', {
operation: 'payment_failure_recovery',
customerId,
requestId
});
}
static async handleNewSubscription(event: any, logger: any, requestId: string) {
const customerId = event.payload?.customer;
const planId = event.payload?.plan?.id;
logger.info('New subscription - setting up customer account', {
operation: 'subscription_setup',
customerId,
planId,
requestId
});
// Simulate account setup
await new Promise(resolve => setTimeout(resolve, 300));
logger.info('Customer account setup completed', {
operation: 'subscription_setup_completed',
customerId,
planId,
requestId
});
}
static async handleCancelledSubscription(event: any, logger: any, requestId: string) {
const customerId = event.payload?.customer;
logger.info('Subscription cancelled - processing offboarding', {
operation: 'subscription_cancellation',
customerId,
requestId
});
// Simulate offboarding
await new Promise(resolve => setTimeout(resolve, 150));
logger.info('Customer offboarding completed', {
operation: 'subscription_cancellation_completed',
customerId,
requestId
});
}
// Order management service
static async processOrder(event: any, logger: any) {
const requestId = `order_${Date.now()}`;
logger.info('Processing order event', {
operation: 'order_processing',
eventType: event.type,
eventId: event.id,
requestId
});
switch (event.type) {
case 'orders/create':
await this.handleNewOrder(event, logger, requestId);
break;
case 'orders/paid':
await this.handleOrderPaid(event, logger, requestId);
break;
case 'orders/fulfilled':
await this.handleOrderFulfilled(event, logger, requestId);
break;
case 'orders/cancelled':
await this.handleOrderCancelled(event, logger, requestId);
break;
default:
logger.info(`Unhandled order event: ${event.type}`, {
operation: 'unhandled_order_event',
eventType: event.type,
requestId
});
}
}
static async handleNewOrder(event: any, logger: any, requestId: string) {
const orderId = event.payload?.id;
const customerId = event.payload?.customer?.id;
logger.info('New order received - processing', {
operation: 'new_order',
orderId,
customerId,
requestId
});
// Simulate inventory check
await new Promise(resolve => setTimeout(resolve, 150));
logger.info('Order processing completed', {
operation: 'new_order_completed',
orderId,
customerId,
requestId
});
}
static async handleOrderPaid(event: any, logger: any, requestId: string) {
const orderId = event.payload?.id;
logger.info('Order paid - initiating fulfillment', {
operation: 'order_paid',
orderId,
requestId
});
// Simulate fulfillment process
await new Promise(resolve => setTimeout(resolve, 250));
logger.info('Order fulfillment initiated', {
operation: 'order_paid_completed',
orderId,
requestId
});
}
static async handleOrderFulfilled(event: any, logger: any, requestId: string) {
const orderId = event.payload?.id;
logger.info('Order fulfilled - sending confirmation', {
operation: 'order_fulfilled',
orderId,
requestId
});
// Simulate confirmation email
await new Promise(resolve => setTimeout(resolve, 100));
logger.info('Order fulfillment confirmation sent', {
operation: 'order_fulfilled_completed',
orderId,
requestId
});
}
static async handleOrderCancelled(event: any, logger: any, requestId: string) {
const orderId = event.payload?.id;
logger.info('Order cancelled - processing refund', {
operation: 'order_cancelled',
orderId,
requestId
});
// Simulate refund process
await new Promise(resolve => setTimeout(resolve, 200));
logger.info('Order cancellation processed', {
operation: 'order_cancelled_completed',
orderId,
requestId
});
}
// Deployment service
static async processDeployment(event: any, logger: any) {
const requestId = `deploy_${Date.now()}`;
logger.info('Processing deployment event', {
operation: 'deployment_processing',
eventType: event.type,
eventId: event.id,
requestId
});
const repo = event.payload?.repository?.name || 'unknown';
switch (event.type) {
case 'push':
await this.handleCodePush(event, logger, requestId, repo);
break;
case 'pull_request':
await this.handlePullRequest(event, logger, requestId, repo);
break;
case 'deployment':
await this.handleDeployment(event, logger, requestId, repo);
break;
default:
logger.info(`Unhandled deployment event: ${event.type}`, {
operation: 'unhandled_deployment_event',
eventType: event.type,
requestId
});
}
}
static async handleCodePush(event: any, logger: any, requestId: string, repo: string) {
logger.info('Code push detected - triggering CI/CD', {
operation: 'code_push',
repository: repo,
requestId
});
// Simulate CI/CD pipeline
await new Promise(resolve => setTimeout(resolve, 500));
logger.info('CI/CD pipeline completed', {
operation: 'code_push_completed',
repository: repo,
requestId
});
}
static async handlePullRequest(event: any, logger: any, requestId: string, repo: string) {
const action = event.payload?.action;
logger.info('Pull request event - running tests', {
operation: 'pull_request',
repository: repo,
action,
requestId
});
// Simulate test execution
await new Promise(resolve => setTimeout(resolve, 300));
logger.info('Pull request tests completed', {
operation: 'pull_request_completed',
repository: repo,
action,
requestId
});
}
static async handleDeployment(event: any, logger: any, requestId: string, repo: string) {
logger.info('Deployment started', {
operation: 'deployment',
repository: repo,
requestId
});
// Simulate deployment
await new Promise(resolve => setTimeout(resolve, 1000));
logger.info('Deployment completed successfully', {
operation: 'deployment_completed',
repository: repo,
requestId
});
}
// Email service
static async processEmail(event: any, logger: any) {
const requestId = `email_${Date.now()}`;
logger.info('Processing email event', {
operation: 'email_processing',
eventType: event.type,
eventId: event.id,
requestId
});
switch (event.type) {
case 'delivered':
await this.handleEmailDelivered(event, logger, requestId);
break;
case 'bounce':
await this.handleEmailBounce(event, logger, requestId);
break;
case 'open':
await this.handleEmailOpen(event, logger, requestId);
break;
case 'click':
await this.handleEmailClick(event, logger, requestId);
break;
default:
logger.info(`Unhandled email event: ${event.type}`, {
operation: 'unhandled_email_event',
eventType: event.type,
requestId
});
}
}
static async handleEmailDelivered(event: any, logger: any, requestId: string) {
const email = event.payload?.email;
logger.info('Email delivered successfully', {
operation: 'email_delivered',
email,
requestId
});
}
static async handleEmailBounce(event: any, logger: any, requestId: string) {
const email = event.payload?.email;
const reason = event.payload?.reason;
logger.warn('Email bounced - updating contact status', {
operation: 'email_bounce',
email,
reason,
requestId
});
// Simulate contact status update
await new Promise(resolve => setTimeout(resolve, 100));
logger.info('Contact status updated for bounced email', {
operation: 'email_bounce_completed',
email,
requestId
});
}
static async handleEmailOpen(event: any, logger: any, requestId: string) {
const email = event.payload?.email;
logger.info('Email opened - tracking engagement', {
operation: 'email_open',
email,
requestId
});
// Log engagement metrics
logger.performance({
level: 'info',
operation: 'email_engagement',
duration: 0,
metrics: {
engagementType: 'open',
email
},
metadata: { requestId }
});
}
static async handleEmailClick(event: any, logger: any, requestId: string) {
const email = event.payload?.email;
const url = event.payload?.url;
logger.info('Email link clicked - tracking conversion', {
operation: 'email_click',
email,
url,
requestId
});
// Log conversion metrics
logger.performance({
level: 'info',
operation: 'email_conversion',
duration: 0,
metrics: {
engagementType: 'click',
email,
url
},
metadata: { requestId }
});
}
}
// ==========================================
// 4. MIDDLEWARE & SECURITY
// ==========================================
// Raw body parser for webhook signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));
// Request logging middleware
app.use((req: any, res, next) => {
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
req.requestId = requestId;
logger.info(`${req.method} ${req.path}`, {
operation: 'request_received',
source: 'express',
requestId,
custom: {
userAgent: req.get('User-Agent'),
ip: req.ip,
contentType: req.get('Content-Type'),
contentLength: req.get('Content-Length')
}
});
next();
});
// Security logging middleware
app.use('/webhooks', (req: any, res, next) => {
const suspiciousPatterns = [
'script',
'eval',
'javascript:',
'<script',
'onload=',
'onerror='
];
const bodyStr = req.body?.toString() || '';
const hasSuspiciousContent = suspiciousPatterns.some(pattern =>
bodyStr.toLowerCase().includes(pattern)
);
if (hasSuspiciousContent) {
logger.security({
level: 'warn',
securityEvent: 'suspicious_payload',
source: req.ip || 'unknown',
severity: 'medium',
details: {
ip: req.ip,
userAgent: req.get('User-Agent'),
endpoint: req.path,
reason: 'Suspicious content detected in payload'
},
metadata: { requestId: req.requestId }
});
}
next();
});
// ==========================================
// 5. WEBHOOK ENDPOINTS
// ==========================================
// Generic webhook handler with full feature demonstration
app.post('/webhooks/:provider', async (req: any, res: any) => {
const provider = req.params.provider;
const startTime = Date.now();
const requestId = req.requestId;
// Create child logger with request context
const requestLogger = logger.child({
requestId,
operation: 'webhook_processing',
source: provider
});
try {
// Validate provider
const config = webhookConfigs[provider as keyof typeof webhookConfigs];
if (!config) {
logger.security({
level: 'warn',
securityEvent: 'unsupported_provider',
source: req.ip || 'unknown',
severity: 'low',
details: {
ip: req.ip,
reason: `Unsupported provider: ${provider}`,
action: `Supported: ${Object.keys(webhookConfigs).join(', ')}`
},
metadata: { requestId }
});
return res.status(400).json({
error: `Unsupported provider: ${provider}`,
supportedProviders: Object.keys(webhookConfigs)
});
}
requestLogger.info('Processing webhook request');
// Process webhook with signature verification
const event = await receiveWebhook(req, config);
// Log webhook reception
logger.webhook({
level: 'info',
source: provider,
operation: 'webhook_received',
duration: 0,
status: 'success',
metadata: {
eventType: event.type,
eventId: event.id,
requestId,
payloadSize: JSON.stringify(event.payload).length
}
});
// Route to appropriate service
switch (provider) {
case 'stripe':
await EcommerceServices.processPayment(event, requestLogger);
break;
case 'shopify':
await EcommerceServices.processOrder(event, requestLogger);
break;
case 'github':
await EcommerceServices.processDeployment(event, requestLogger);
break;
case 'sendgrid':
await EcommerceServices.processEmail(event, requestLogger);
break;
}
const duration = Date.now() - startTime;
// Log successful processing
logger.webhook({
level: 'info',
source: provider,
operation: 'webhook_processed',
duration,
status: 'success',
metadata: {
eventType: event.type,
eventId: event.id,
requestId
}
});
// Log performance metrics
logger.performance({
level: 'info',
operation: 'webhook_processing',
duration,
metrics: {
memoryUsage: process.memoryUsage().heapUsed,
requestCount: 1
},
metadata: { requestId }
});
requestLogger.info('Webhook processed successfully', {
custom: { duration: `${duration}ms` }
});
res.status(200).json({
success: true,
provider,
eventId: event.id,
eventType: event.type,
requestId,
processingTime: `${duration}ms`
});
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = (error as Error).message;
// Log error with full context
requestLogger.error('Webhook processing failed', error as Error, {
custom: {
duration: `${duration}ms`,
errorType: (error as Error).name
}
});
// Log webhook failure
logger.webhook({
level: 'error',
source: provider,
operation: 'webhook_processed',
duration,
status: 'failure',
metadata: {
error: errorMessage,
requestId,
errorType: (error as Error).name
}
});
// Log security event for signature errors
if (errorMessage.includes('signature')) {
logger.security({
level: 'warn',
securityEvent: 'invalid_signature',
source: req.ip || 'unknown',
severity: 'medium',
details: {
ip: req.ip,
userAgent: req.get('User-Agent'),
endpoint: req.path,
reason: `Invalid webhook signature for ${provider}`
},
metadata: { requestId }
});
}
res.status(400).json({
error: 'Webhook processing failed',
provider,
message: errorMessage,
requestId,
processingTime: `${duration}ms`
});
}
});
// ==========================================
// 6. MONITORING & HEALTH ENDPOINTS
// ==========================================
// Comprehensive status endpoint
app.get('/status', (req: any, res) => {
const uptime = process.uptime();
const memoryUsage = process.memoryUsage();
logger.info('Status check requested', {
operation: 'status_check',
requestId: req.requestId
});
res.json({
service: 'ecommerce-webhook-platform',
version: '1.0.0',
status: 'healthy',
uptime: `${Math.floor(uptime / 60)}m ${Math.floor(uptime % 60)}s`,
memory: {
used: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`,
total: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)}MB`
},
providers: {
supported: Object.keys(webhookConfigs),
available: Object.keys(adapters),
endpoints: Object.keys(webhookConfigs).map(provider => ({
provider,
endpoint: `/webhooks/${provider}`,
method: 'POST'
}))
},
features: {
structuredLogging: true,
securityMonitoring: true,
performanceTracking: true,
multiTenant: true,
cliIntegration: true
},
logging: {
level: 'info',
outputs: ['console', 'file'],
logFile: './logs/ecommerce-platform.log'
}
});
});
// Health check endpoint
app.get('/health', (req: any, res) => {
logger.info('Health check requested', {
operation: 'health_check',
requestId: req.requestId
});
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
providers: Object.keys(webhookConfigs).length,
uptime: process.uptime()
});
});
// Metrics endpoint
app.get('/metrics', (req: any, res) => {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
logger.performance({
level: 'info',
operation: 'metrics_request',
duration: 0,
metrics: {
memoryUsage: memoryUsage.heapUsed,
cpuUsage: cpuUsage.user
},
metadata: { requestId: req.requestId }
});
res.json({
timestamp: new Date().toISOString(),
memory: {
heapUsed: memoryUsage.heapUsed,
heapTotal: memoryUsage.heapTotal,
external: memoryUsage.external,
rss: memoryUsage.rss
},
cpu: {
user: cpuUsage.user,
system: cpuUsage.system
},
uptime: process.uptime(),
loadAverage: require('os').loadavg()
});
});
// ==========================================
// 7. GRACEFUL SHUTDOWN
// ==========================================
async function gracefulShutdown(signal: string) {
logger.info(`Received ${signal}, shutting down gracefully...`, {
operation: 'server_shutdown',
custom: { signal }
});
try {
// Flush logs
await logger.flush();
logger.info('E-commerce platform shutdown complete', {
operation: 'server_shutdown_completed'
});
process.exit(0);
} catch (error) {
logger.error('Error during shutdown', error as Error);
process.exit(1);
}
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// ==========================================
// 8. SERVER STARTUP
// ==========================================
app.listen(PORT, () => {
logger.info(`E-commerce webhook platform started on port ${PORT}`, {
operation: 'server_start',
source: 'express',
custom: { port: PORT }
});
console.log('🚀 E-commerce Webhook Platform Started!');
console.log(`📊 Server running on port ${PORT}`);
console.log('\n📥 Webhook Endpoints:');
Object.keys(webhookConfigs).forEach(provider => {
console.log(` ${provider.toUpperCase()}: POST http://localhost:${PORT}/webhooks/${provider}`);
});
console.log('\n📊 Monitoring Endpoints:');
console.log(` Status: GET http://localhost:${PORT}/status`);
console.log(` Health: GET http://localhost:${PORT}/health`);
console.log(` Metrics: GET http://localhost:${PORT}/metrics`);
console.log('\n📄 Logs:');
console.log(` File: ./logs/ecommerce-platform.log`);
console.log(` Format: Structured JSON`);
console.log('\n🧪 Test Examples:');
console.log(` curl http://localhost:${PORT}/status`);
console.log(` curl -X POST http://localhost:${PORT}/webhooks/stripe -H "Content-Type: application/json" -d '{"test":"payment"}'`);
console.log(` curl -X POST http://localhost:${PORT}/webhooks/shopify -H "Content-Type: application/json" -d '{"test":"order"}'`);
console.log(` curl -X POST http://localhost:${PORT}/webhooks/github -H "Content-Type: application/json" -d '{"test":"deployment"}'`);
console.log(` curl -X POST http://localhost:${PORT}/webhooks/sendgrid -H "Content-Type: application/json" -d '{"test":"email"}'`);
console.log('\n✨ Features Enabled:');
console.log(' ✅ Multiple webhook adapters (Stripe, Shopify, GitHub, SendGrid)');
console.log(' ✅ Structured JSON logging with file rotation');
console.log(' ✅ Security monitoring and threat detection');
console.log(' ✅ Performance tracking and metrics');
console.log(' ✅ Error handling and recovery');
console.log(' ✅ Request tracing and correlation');
console.log(' ✅ Graceful shutdown with log flushing');
});
export default app;