{"id":28936288,"url":"https://github.com/lanemc/unified-webhook-router","last_synced_at":"2026-04-09T17:04:57.530Z","repository":{"id":300045573,"uuid":"1005029119","full_name":"lanemc/unified-webhook-router","owner":"lanemc","description":"🎯 Developer-friendly webhook router for Stripe, GitHub, Slack, Twilio, Square. Framework-agnostic, serverless-ready, dual-language (TypeScript/Python).","archived":false,"fork":false,"pushed_at":"2025-06-19T15:45:01.000Z","size":123,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-19T16:27:04.520Z","etag":null,"topics":["crypto","django","express","fastapi","flask","github","hmac","nextjs","python","security","serverless","slack","stripe","typescript","webhook"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lanemc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-19T14:53:07.000Z","updated_at":"2025-06-19T15:45:04.000Z","dependencies_parsed_at":"2025-06-19T16:27:21.358Z","dependency_job_id":"aa7fc722-fa17-448e-b31f-8a4afb6a63f0","html_url":"https://github.com/lanemc/unified-webhook-router","commit_stats":null,"previous_names":["lanemc/unified-webhook-router"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/lanemc/unified-webhook-router","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lanemc%2Funified-webhook-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lanemc%2Funified-webhook-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lanemc%2Funified-webhook-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lanemc%2Funified-webhook-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lanemc","download_url":"https://codeload.github.com/lanemc/unified-webhook-router/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lanemc%2Funified-webhook-router/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261358697,"owners_count":23146674,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crypto","django","express","fastapi","flask","github","hmac","nextjs","python","security","serverless","slack","stripe","typescript","webhook"],"created_at":"2025-06-22T20:08:17.974Z","updated_at":"2025-12-30T19:57:21.022Z","avatar_url":"https://github.com/lanemc.png","language":"Python","readme":"# 🎯 Unified Webhook Router\n\n**The developer-friendly way to handle webhooks from any provider. Secure by default, framework agnostic, and delightfully simple.**\n\n[![npm version](https://badge.fury.io/js/unified-webhook-router.svg)](https://badge.fury.io/js/unified-webhook-router)\n[![PyPI version](https://badge.fury.io/py/unified-webhook-router.svg)](https://badge.fury.io/py/unified-webhook-router)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)\n[![Python](https://img.shields.io/badge/Python-3.7+-blue.svg)](https://www.python.org/)\n\n---\n\n## 🚀 Why You'll Love This\n\n**Before:** Wrestling with different webhook formats, security implementations, and provider-specific quirks.\n\n```javascript\n// The old way: different code for every provider 😫\napp.post('/stripe-webhooks', (req, res) =\u003e {\n  const sig = req.headers['stripe-signature'];\n  // Manually implement Stripe's signature verification...\n  const payload = stripe.webhooks.constructEvent(body, sig, secret);\n  // Handle Stripe events...\n});\n\napp.post('/github-webhooks', (req, res) =\u003e {\n  const sig = req.headers['x-hub-signature-256'];\n  // Manually implement GitHub's different signature scheme...\n  // Handle GitHub events...\n});\n\n// And so on for every provider... 🤯\n```\n\n**After:** One beautiful API that handles everything securely.\n\n```javascript\n// The new way: one router, all providers, zero security worries ✨\nconst router = new WebhookRouter({\n  stripe: { signingSecret: process.env.STRIPE_SIGNING_SECRET },\n  github: { secret: process.env.GITHUB_WEBHOOK_SECRET },\n  slack: { signingSecret: process.env.SLACK_SIGNING_SECRET }\n});\n\nrouter.on('stripe', 'payment_intent.succeeded', async (event) =\u003e {\n  console.log(`💳 Payment ${event.payload.id} succeeded!`);\n});\n\nrouter.on('github', 'push', async (event) =\u003e {\n  console.log(`🚀 New push to ${event.payload.repository.full_name}`);\n});\n\n// One endpoint handles everything\napp.post('/webhooks', express.raw({ type: '*/*' }), (req, res) =\u003e {\n  router.handle(req, res);\n});\n```\n\n---\n\n## ✨ Features That Make Development a Joy\n\n- 🔐 **Security Built-In**: HMAC verification, replay protection, timing-safe comparisons—all automated\n- 🚀 **Framework Agnostic**: Works with Express, Next.js, Flask, Django, FastAPI, and more\n- ☁️ **Serverless Ready**: Perfect for AWS Lambda, Vercel, Netlify Functions\n- 🔌 **Provider Rich**: Stripe, GitHub, Slack, Twilio, Square + easy custom providers\n- 📦 **Dual Language**: TypeScript/Node.js and Python with identical APIs\n- ⚡ **Type Safe**: Full TypeScript definitions and Python type hints\n- 🛡️ **Zero Trust**: Every webhook is verified before your code runs\n- 🎯 **Developer First**: Designed for happiness, not just functionality\n\n---\n\n## 📦 Installation\n\n### TypeScript/Node.js\n```bash\nnpm install unified-webhook-router\n# or\nyarn add unified-webhook-router\n```\n\n### Python\n```bash\npip install unified-webhook-router\n```\n\n---\n\n## 🏁 Quick Start\n\n### TypeScript/Node.js\n\n```typescript\nimport express from 'express';\nimport { WebhookRouter } from 'unified-webhook-router';\n\nconst app = express();\nconst router = new WebhookRouter({\n  stripe: { signingSecret: process.env.STRIPE_SIGNING_SECRET! },\n  github: { secret: process.env.GITHUB_WEBHOOK_SECRET! }\n});\n\n// Handle successful payments\nrouter.on('stripe', 'payment_intent.succeeded', async (event) =\u003e {\n  const payment = event.payload;\n  console.log(`💰 Received $${payment.amount / 100} from ${payment.customer}`);\n  \n  // Your business logic here\n  await fulfillOrder(payment.metadata.order_id);\n});\n\n// Handle code pushes\nrouter.on('github', 'push', async (event) =\u003e {\n  const { repository, commits } = event.payload;\n  console.log(`📝 ${commits.length} commits pushed to ${repository.full_name}`);\n  \n  // Trigger your CI/CD pipeline\n  await triggerDeployment(repository.full_name, commits);\n});\n\n// Single endpoint for all webhooks\napp.post('/webhooks', express.raw({ type: '*/*' }), (req, res) =\u003e {\n  router.handle(req, res);\n});\n\napp.listen(3000, () =\u003e {\n  console.log('🎯 Webhook server ready at http://localhost:3000');\n});\n```\n\n### Python\n\n```python\nfrom flask import Flask, request\nfrom unified_webhook_router import WebhookRouter, InvalidWebhookError\n\napp = Flask(__name__)\nrouter = WebhookRouter({\n    'stripe': {'signing_secret': os.environ['STRIPE_SIGNING_SECRET']},\n    'github': {'secret': os.environ['GITHUB_WEBHOOK_SECRET']}\n})\n\n@router.on('stripe', 'payment_intent.succeeded')\nasync def handle_payment(event):\n    payment = event.payload\n    print(f\"💰 Received ${payment['amount'] / 100} from {payment['customer']}\")\n    \n    # Your business logic here\n    await fulfill_order(payment['metadata']['order_id'])\n\n@router.on('github', 'push')\nasync def handle_push(event):\n    repo = event.payload['repository']['full_name']\n    commits = event.payload['commits']\n    print(f\"📝 {len(commits)} commits pushed to {repo}\")\n    \n    # Trigger your CI/CD pipeline\n    await trigger_deployment(repo, commits)\n\n@app.route('/webhooks', methods=['POST'])\nasync def webhooks():\n    try:\n        result = await router.handle_request(request)\n        return result or '', 200\n    except InvalidWebhookError:\n        return 'Invalid webhook', 400\n\nif __name__ == '__main__':\n    app.run(port=3000)\n```\n\n---\n\n## 🎪 Framework Examples\n\n### Next.js API Routes\n\n```typescript\n// pages/api/webhooks.ts\nimport { WebhookRouter } from 'unified-webhook-router';\n\nconst router = new WebhookRouter({\n  stripe: { signingSecret: process.env.STRIPE_SIGNING_SECRET! }\n});\n\nrouter.on('stripe', 'checkout.session.completed', async (event) =\u003e {\n  // Handle successful checkout\n  await processOrder(event.payload);\n});\n\nexport const config = {\n  api: { bodyParser: false } // Important: disable body parsing\n};\n\nexport default function handler(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method === 'POST') {\n    return router.handle(req, res);\n  }\n  res.status(405).end();\n}\n```\n\n### AWS Lambda\n\n```typescript\nimport { WebhookRouter } from 'unified-webhook-router';\n\nconst router = new WebhookRouter({\n  stripe: { signingSecret: process.env.STRIPE_SIGNING_SECRET! }\n});\n\nrouter.on('stripe', '*', async (event) =\u003e {\n  console.log(`Stripe event: ${event.type}`);\n});\n\nexport const handler = async (event: APIGatewayProxyEvent) =\u003e {\n  try {\n    // Convert Lambda event to standard request format\n    const mockReq = {\n      headers: event.headers,\n      body: Buffer.from(event.body || '', event.isBase64Encoded ? 'base64' : 'utf8')\n    };\n    \n    const result = await router.handleLambda(mockReq);\n    return {\n      statusCode: 200,\n      body: result ? JSON.stringify(result) : ''\n    };\n  } catch (error) {\n    return {\n      statusCode: 400,\n      body: 'Invalid webhook'\n    };\n  }\n};\n```\n\n### Django\n\n```python\nfrom django.http import HttpResponse\nfrom django.views.decorators.csrf import csrf_exempt\nfrom unified_webhook_router import WebhookRouter, InvalidWebhookError\n\nrouter = WebhookRouter({\n    'stripe': {'signing_secret': os.environ['STRIPE_SIGNING_SECRET']}\n})\n\n@router.on('stripe', 'invoice.paid')\ndef handle_invoice_paid(event):\n    invoice = event.payload\n    print(f\"📧 Invoice {invoice['id']} paid: ${invoice['amount_paid'] / 100}\")\n\n@csrf_exempt\ndef webhooks(request):\n    if request.method == 'POST':\n        try:\n            result = router.handle_request(request)\n            return HttpResponse(result or '', status=200)\n        except InvalidWebhookError:\n            return HttpResponse('Invalid webhook', status=400)\n    return HttpResponse('Method not allowed', status=405)\n```\n\n### FastAPI\n\n```python\nfrom fastapi import FastAPI, Request, HTTPException\nfrom unified_webhook_router import WebhookRouter, InvalidWebhookError\n\napp = FastAPI()\nrouter = WebhookRouter({\n    'stripe': {'signing_secret': os.environ['STRIPE_SIGNING_SECRET']}\n})\n\n@router.on('stripe', 'customer.subscription.created')\nasync def handle_new_subscription(event):\n    subscription = event.payload\n    print(f\"🎉 New subscription: {subscription['id']}\")\n\n@app.post(\"/webhooks\")\nasync def webhooks(request: Request):\n    try:\n        result = await router.handle_request(request)\n        return result or {}\n    except InvalidWebhookError:\n        raise HTTPException(status_code=400, detail=\"Invalid webhook\")\n```\n\n---\n\n## 🔐 Supported Providers\n\n| Provider | Verification Method | Special Features |\n|----------|-------------------|------------------|\n| **Stripe** | HMAC SHA-256 + timestamp | ✅ Replay protection, tolerance config |\n| **GitHub** | HMAC SHA-1/256 | ✅ Algorithm selection, delivery ID tracking |\n| **Slack** | HMAC SHA-256 + timestamp | ✅ URL verification challenges, slash commands |\n| **Twilio** | HMAC SHA-1 + URL validation | ✅ Form/JSON payload support |\n| **Square** | HMAC SHA-256 + URL | ✅ Notification URL verification |\n\n### Provider Configuration\n\n```typescript\nconst router = new WebhookRouter({\n  stripe: {\n    signingSecret: 'whsec_...',\n    tolerance: 300 // Optional: 5 minutes (default)\n  },\n  github: {\n    secret: 'your-github-secret',\n    algorithm: 'sha256' // Optional: 'sha256' or 'sha1'\n  },\n  slack: {\n    signingSecret: 'your-slack-secret',\n    tolerance: 300 // Optional: 5 minutes (default)\n  },\n  twilio: {\n    authToken: 'your-twilio-auth-token'\n  },\n  square: {\n    signatureKey: 'your-square-signature-key',\n    notificationUrl: 'https://yourdomain.com/webhooks'\n  }\n});\n```\n\n---\n\n## 🎯 Event Handling\n\n### Basic Handlers\n\n```typescript\n// Handle specific events\nrouter.on('stripe', 'payment_intent.succeeded', handlePayment);\nrouter.on('github', 'push', handlePush);\nrouter.on('slack', 'reaction_added', handleReaction);\n\n// Wildcard handlers for all events from a provider\nrouter.on('stripe', '*', (event) =\u003e {\n  console.log(`Stripe event: ${event.type}`);\n});\n\n// Multiple handlers\nrouter.on('github', 'push', logPush);\nrouter.on('github', 'push', triggerCI);\nrouter.on('github', 'push', notifyTeam);\n```\n\n### Event Object Structure\n\nEvery handler receives a normalized event object:\n\n```typescript\ninterface WebhookEvent\u003cT = any\u003e {\n  provider: string;     // 'stripe', 'github', etc.\n  type: string;         // 'payment_intent.succeeded', 'push', etc.\n  id?: string;          // Event ID when available\n  payload: T;           // The parsed webhook payload\n  rawHeaders: Record\u003cstring, string\u003e;\n  rawBody: string;      // Original request body\n  receivedAt: Date;     // When we received it\n}\n```\n\n### Async Handlers\n\n```typescript\n// TypeScript/Node.js\nrouter.on('stripe', 'payment_intent.succeeded', async (event) =\u003e {\n  await updateDatabase(event.payload);\n  await sendConfirmationEmail(event.payload.customer);\n  await triggerFulfillment(event.payload.metadata.order_id);\n});\n\n# Python\n@router.on('stripe', 'payment_intent.succeeded')\nasync def handle_payment(event):\n    await update_database(event.payload)\n    await send_confirmation_email(event.payload['customer'])\n    await trigger_fulfillment(event.payload['metadata']['order_id'])\n```\n\n### Response Handling\n\nSome webhooks expect specific responses:\n\n```typescript\n// Slack slash commands\nrouter.on('slack', '/deploy', (event) =\u003e {\n  const { user_name, text } = event.payload;\n  \n  // Trigger deployment\n  triggerDeploy(text);\n  \n  // Respond to Slack\n  return {\n    text: `🚀 Deployment of \\`${text}\\` started by ${user_name}!`,\n    response_type: 'in_channel'\n  };\n});\n\n// Slack URL verification (handled automatically)\n// The router automatically responds to Slack's challenge requests\n```\n\n---\n\n## 🔧 Advanced Usage\n\n### Custom Providers\n\nAdd support for any webhook provider:\n\n```typescript\nimport { WebhookProvider } from 'unified-webhook-router';\n\nconst customProvider: WebhookProvider = {\n  name: 'myservice',\n  \n  identify: (headers) =\u003e {\n    return 'x-myservice-signature' in headers;\n  },\n  \n  verify: (headers, rawBody, config) =\u003e {\n    const signature = headers['x-myservice-signature'];\n    const expected = computeHMAC('sha256', config.secret, rawBody);\n    return timingSafeCompare(signature, expected);\n  },\n  \n  extractEventType: (headers, payload) =\u003e {\n    return payload.event_type;\n  },\n  \n  parsePayload: (rawBody) =\u003e {\n    return JSON.parse(rawBody.toString('utf8'));\n  }\n};\n\nrouter.registerProvider(customProvider);\n\n// Now you can use it\nrouter.on('myservice', 'user.created', handleNewUser);\n```\n\n### Environment Configuration\n\n```typescript\n// Use environment variables for secrets\nconst router = new WebhookRouter({\n  stripe: { \n    signingSecret: process.env.STRIPE_SIGNING_SECRET \n  },\n  github: { \n    secret: process.env.GITHUB_WEBHOOK_SECRET \n  },\n  slack: { \n    signingSecret: process.env.SLACK_SIGNING_SECRET \n  }\n});\n\n// Or use a configuration object\nconst config = {\n  stripe: { signingSecret: getSecret('stripe') },\n  github: { secret: getSecret('github') }\n};\n```\n\n### Error Handling\n\n```typescript\n// Custom error handling\nrouter.on('stripe', 'payment_intent.succeeded', async (event) =\u003e {\n  try {\n    await processPayment(event.payload);\n  } catch (error) {\n    console.error('Payment processing failed:', error);\n    await logFailure(event, error);\n    throw error; // Re-throw to trigger webhook retry\n  }\n});\n\n// Global error handler\nrouter.onError((error, event) =\u003e {\n  console.error(`Webhook error for ${event.provider}/${event.type}:`, error);\n  notifyTeam(error, event);\n});\n```\n\n### Testing Webhooks\n\n```typescript\n// Create test events for unit testing\nconst testEvent = router.createTestEvent('stripe', 'payment_intent.succeeded', {\n  id: 'pi_test_123',\n  amount: 2000,\n  currency: 'usd',\n  status: 'succeeded'\n});\n\nawait myHandler(testEvent);\n```\n\n---\n\n## 🛡️ Security Features\n\nThe router implements security best practices automatically:\n\n### ✅ What's Protected\n\n- **Signature Verification**: Every webhook is cryptographically verified\n- **Replay Attack Prevention**: Timestamp validation prevents old requests\n- **Timing Attack Prevention**: Constant-time comparison prevents timing analysis\n- **Raw Body Integrity**: Signatures computed on exact received bytes\n- **Secret Safety**: No secrets logged or exposed in errors\n\n### 🔒 Security Details by Provider\n\n**Stripe:**\n- Verifies `Stripe-Signature` header using HMAC SHA-256\n- Validates timestamp within tolerance window (default: 5 minutes)\n- Supports multiple signature versions\n\n**GitHub:**\n- Verifies `X-Hub-Signature-256` (preferred) or `X-Hub-Signature`\n- Uses HMAC SHA-256 or SHA-1 with your webhook secret\n- Validates against raw request body\n\n**Slack:**\n- Verifies `X-Slack-Signature` using HMAC SHA-256\n- Validates `X-Slack-Request-Timestamp` within tolerance\n- Automatically handles URL verification challenges\n\n**Twilio:**\n- Verifies `X-Twilio-Signature` using HMAC SHA-1\n- Validates against URL + sorted parameters\n- Supports both JSON and form-encoded payloads\n\n**Square:**\n- Verifies `X-Square-Hmacsha256-Signature` using HMAC SHA-256\n- Validates against notification URL + request body\n- Base64 signature decoding\n\n---\n\n## 🚨 Error Handling\n\nThe router provides clear error handling:\n\n```typescript\ntry {\n  await router.handle(req, res);\n} catch (error) {\n  if (error instanceof InvalidWebhookError) {\n    // Invalid signature, expired timestamp, etc.\n    console.log('Webhook rejected:', error.message);\n    res.status(400).send('Invalid webhook');\n  } else {\n    // Handler error\n    console.error('Processing error:', error);\n    res.status(500).send('Processing failed');\n  }\n}\n```\n\n### Common Error Scenarios\n\n- **Invalid Signature**: Wrong secret or corrupted payload\n- **Expired Timestamp**: Request older than tolerance window\n- **Unknown Provider**: No matching provider found\n- **Missing Configuration**: Provider not configured\n- **Handler Error**: Exception in your handler code\n\n---\n\n## 📊 Logging\n\nEnable detailed logging for debugging:\n\n```typescript\nimport { WebhookRouter, createLogger } from 'unified-webhook-router';\n\nconst logger = createLogger({\n  level: 'debug',\n  format: 'json'\n});\n\nconst router = new WebhookRouter(config, logger);\n\n// Logs include:\n// - Incoming webhook identification\n// - Verification success/failure  \n// - Handler execution\n// - Performance metrics\n```\n\n---\n\n## 🎭 Framework Integration Tips\n\n### Getting Raw Body\n\nMost frameworks parse request bodies by default, but webhook verification requires the raw bytes:\n\n```typescript\n// Express: Use raw middleware\napp.use('/webhooks', express.raw({ type: '*/*' }));\n\n// Next.js: Disable body parser\nexport const config = { api: { bodyParser: false } };\n\n// Koa: Use raw-body middleware\napp.use(bodyParser({ enableTypes: ['text'] }));\n```\n\n### CORS and Headers\n\n```typescript\n// If needed, configure CORS for webhook endpoints\napp.use('/webhooks', cors({\n  origin: false, // Webhooks don't need CORS\n  credentials: false\n}));\n```\n\n### Rate Limiting\n\n```typescript\n// Protect webhook endpoints from abuse\napp.use('/webhooks', rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100 // limit each IP to 100 requests per windowMs\n}));\n```\n\n---\n\n## 🧪 Testing\n\n### Unit Testing\n\n```typescript\nimport { WebhookRouter } from 'unified-webhook-router';\n\ndescribe('Webhook Handlers', () =\u003e {\n  const router = new WebhookRouter({\n    stripe: { signingSecret: 'test_secret' }\n  });\n\n  it('handles payment success', async () =\u003e {\n    const mockEvent = router.createTestEvent('stripe', 'payment_intent.succeeded', {\n      id: 'pi_test',\n      amount: 1000\n    });\n\n    const result = await myPaymentHandler(mockEvent);\n    expect(result).toBeDefined();\n  });\n});\n```\n\n### Integration Testing\n\n```typescript\n// Test with real webhook payloads\nconst stripePayload = JSON.stringify({\n  id: 'evt_test',\n  type: 'payment_intent.succeeded',\n  data: { object: { id: 'pi_test', amount: 1000 } }\n});\n\nconst signature = generateStripeSignature(stripePayload, secret);\n\nconst mockReq = {\n  headers: { 'stripe-signature': signature },\n  body: Buffer.from(stripePayload)\n};\n\nawait router.handle(mockReq, mockRes);\n```\n\n---\n\n## ⚡ Performance Tips\n\n### Optimize for High Throughput\n\n```typescript\n// Use connection pooling for database operations\nconst pool = new Pool({ connectionString: process.env.DATABASE_URL });\n\nrouter.on('stripe', 'payment_intent.succeeded', async (event) =\u003e {\n  const client = await pool.connect();\n  try {\n    await client.query('INSERT INTO payments ...', [event.payload.id]);\n  } finally {\n    client.release();\n  }\n});\n\n// Use queues for heavy processing\nrouter.on('github', 'push', async (event) =\u003e {\n  await queue.add('deploy', {\n    repository: event.payload.repository.full_name,\n    commit: event.payload.head_commit.id\n  });\n});\n```\n\n### Memory Management\n\n```typescript\n// Avoid storing large objects in memory\nrouter.on('stripe', 'invoice.finalized', async (event) =\u003e {\n  // Process immediately, don't store\n  await generateInvoicePDF(event.payload);\n  \n  // Or queue for later processing\n  await queue.add('invoice-pdf', { invoiceId: event.payload.id });\n});\n```\n\n---\n\n## 🚀 Deployment\n\n### Environment Variables\n\n```bash\n# .env file\nSTRIPE_SIGNING_SECRET=whsec_...\nGITHUB_WEBHOOK_SECRET=your_github_secret\nSLACK_SIGNING_SECRET=your_slack_secret\nTWILIO_AUTH_TOKEN=your_twilio_token\nSQUARE_SIGNATURE_KEY=your_square_key\nSQUARE_NOTIFICATION_URL=https://yourdomain.com/webhooks\n```\n\n### Docker\n\n```dockerfile\nFROM node:18-alpine\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=production\nCOPY . .\nEXPOSE 3000\nCMD [\"npm\", \"start\"]\n```\n\n### Kubernetes\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: webhook-server\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: webhook-server\n  template:\n    metadata:\n      labels:\n        app: webhook-server\n    spec:\n      containers:\n      - name: webhook-server\n        image: your-registry/webhook-server\n        ports:\n        - containerPort: 3000\n        env:\n        - name: STRIPE_SIGNING_SECRET\n          valueFrom:\n            secretKeyRef:\n              name: webhook-secrets\n              key: stripe-signing-secret\n```\n\n---\n\n## 🔍 Troubleshooting\n\n### Common Issues\n\n**\"Invalid signature\" errors:**\n```typescript\n// Check if you're using the raw request body\napp.use('/webhooks', express.raw({ type: '*/*' })); // ✅ Correct\napp.use('/webhooks', express.json()); // ❌ Wrong - parses body\n```\n\n**Timestamp tolerance errors:**\n```typescript\n// Increase tolerance if needed (max recommended: 600 seconds)\nconst router = new WebhookRouter({\n  stripe: { \n    signingSecret: process.env.STRIPE_SIGNING_SECRET,\n    tolerance: 600 // 10 minutes\n  }\n});\n```\n\n**Headers not found:**\n```typescript\n// Ensure headers are lowercase\nconst normalizedHeaders = Object.fromEntries(\n  Object.entries(headers).map(([k, v]) =\u003e [k.toLowerCase(), v])\n);\n```\n\n### Debug Mode\n\n```typescript\nconst router = new WebhookRouter(config, {\n  debug: true, // Enable verbose logging\n  logLevel: 'debug'\n});\n```\n\n### Health Checks\n\n```typescript\n// Add a health check endpoint\napp.get('/health', (req, res) =\u003e {\n  res.json({ \n    status: 'ok', \n    providers: router.getEnabledProviders(),\n    uptime: process.uptime()\n  });\n});\n```\n\n---\n\n## 🤝 Contributing\n\nWe love contributions! Here's how to get started:\n\n1. **Fork the repository**\n2. **Create a feature branch**: `git checkout -b feature/new-provider`\n3. **Add your changes** with tests\n4. **Run the test suite**: `npm test` or `pytest`\n5. **Submit a pull request**\n\n### Adding a New Provider\n\n1. Create provider implementation in `src/providers/`\n2. Add comprehensive tests\n3. Update documentation\n4. Submit PR with example usage\n\n---\n\n## 📄 License\n\nMIT © [Your Name]\n\n---\n\n## 🙏 Acknowledgments\n\nBuilt with inspiration from the webhook handling challenges faced by developers everywhere. Special thanks to:\n\n- The security teams at Stripe, GitHub, Slack, and others for their excellent documentation\n- The Node.js and Python communities for building robust crypto libraries\n- Every developer who's ever struggled with webhook verification\n\n---\n\n## 🔗 Links\n\n- **Documentation**: [docs.example.com](https://docs.example.com)\n- **GitHub**: [github.com/username/unified-webhook-router](https://github.com/username/unified-webhook-router)\n- **NPM**: [npmjs.com/package/unified-webhook-router](https://npmjs.com/package/unified-webhook-router)\n- **PyPI**: [pypi.org/project/unified-webhook-router](https://pypi.org/project/unified-webhook-router)\n- **Issues**: [github.com/username/unified-webhook-router/issues](https://github.com/username/unified-webhook-router/issues)\n\n---\n\n**Made with ❤️ for developers who deserve better webhook handling.**","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flanemc%2Funified-webhook-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flanemc%2Funified-webhook-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flanemc%2Funified-webhook-router/lists"}