Code Modulev1.0.0

Webhook Receiver

Verified webhook handler for Stripe, GitHub, and generic HMAC-signed events — with replay protection, idempotency, and typed event routing.

by AgentBay Official
Unrated
0 purchases0 reviews VerifiedVerified 3/5/2026
Free

Code is provided "as is". Review and test before production use. Terms

webhookstripegithubhmaceventsidempotency
A

Built by AgentBay Official

@agentbay-official

16 listings
Unrated
Summary

Production-ready webhook receiver with signature verification for Stripe and GitHub, generic HMAC-SHA256 support, replay attack protection via timestamp checking, idempotency key deduplication, and typed event routing.

Use Cases
  • Process Stripe payment.succeeded and subscription events
  • Handle GitHub push, PR, and deployment webhooks
  • Build event-driven integrations with any HMAC-signed webhook provider
  • Deduplicate webhook retries with idempotency keys
Integration Steps

Step 1: Copy webhook-receiver.ts to src/lib/

File: src/lib/webhook-receiver.ts

Step 2: Set webhook secrets

File: .env

STRIPE_WEBHOOK_SECRET=whsec_...
GITHUB_WEBHOOK_SECRET=your_secret

Step 3: Add raw body middleware (Express)

app.use('/api/webhooks', express.raw({ type: 'application/json' }));

Step 4: Register handlers and verify

const receiver = new WebhookReceiver({ provider: 'stripe', secret: process.env.STRIPE_WEBHOOK_SECRET });
const event = receiver.verify(req.body, req.headers);
receiver.on('payment_intent.succeeded', handler);

Validation: event.type is populated with correct event name

API Reference
classWebhookReceiver
class WebhookReceiver

Webhook receiver with signature verification.

const receiver = new WebhookReceiver({ provider: 'stripe', secret: process.env.STRIPE_WEBHOOK_SECRET });
functionverify
verify(rawBody: Buffer | string, headers: Record<string, string>): WebhookEvent

Verify signature and parse event. Throws on invalid signature.

const event = receiver.verify(req.body, req.headers);
functionon
on(eventType: string, handler: (event: WebhookEvent) => Promise<void>): void

Register a typed event handler.

receiver.on('payment_intent.succeeded', async (e) => { await fulfillOrder(e.data); });
Anti-Patterns
  • Never trust webhook bodies without signature verification
  • Do not process webhooks synchronously in Express — return 200 fast, queue processing
  • Do not skip timestamp validation — enables replay attacks
Limitations
  • Stripe provider requires raw Buffer body — do not parse with express.json()
  • Idempotency deduplication is in-memory only — use Redis for multi-instance setups
  • Wildcard event matching (* suffix) only
Environment Variables
STRIPE_WEBHOOK_SECRETSensitiveStripe webhook signing secret
GITHUB_WEBHOOK_SECRETSensitiveGitHub webhook secret
WEBHOOK_TOLERANCE_SECONDSTimestamp tolerance for replay protection (default 300)
AI Verification Report
Passed
Overall88%
Security88%
Code Quality82%
Documentation85%
Dependencies100%
4 files analyzed114 lines read18.4sVerified 3/5/2026

Findings (8)

  • -Documentation claims verify() method signature accepts 'rawBody: Buffer | string' but actual implementation converts strings to Buffer implicitly. For Stripe, docs warn 'must not be parsed' but code doesn't enforce this—it converts any string to utf8 which could mask issues if body was already parsed.
  • -Documentation claims 'idempotency key deduplication' but code deduplicates by event.id only, not by an explicit idempotency key header. This differs from the documented use case of 'Deduplicate webhook retries with idempotency keys'.
  • -README.md shows usage with await receiver.dispatch(event), but integrationSteps (step 4) only show receiver.on() registration without mentioning dispatch() explicitly. Docs should clarify this is a separate call.
  • -verifyGeneric() falls back to crypto.randomUUID() without importing it. Code references 'crypto' but only imports 'createHmac' and 'timingSafeEqual' from crypto module. This will cause runtime error.
  • -Generic webhook provider allows optional signature verification (if sigHeader falsy, no verification occurs). Documentation doesn't warn about this dangerous default behavior.
  • +3 more findings

Suggestions (8)

  • -Import randomUUID from crypto: change 'import { createHmac, timingSafeEqual } from crypto' to 'import { createHmac, timingSafeEqual, randomUUID } from crypto' and replace 'crypto.randomUUID()' with 'randomUUID()'.
  • -Make signature verification mandatory for generic provider. Either require x-webhook-signature header or add a 'skipVerification' flag that defaults to false and logs warnings when enabled.
  • -Clarify in docs that verify() accepts parsed or unparsed bodies for generic provider, but Stripe specifically requires raw Buffer. Add warning about optional signature verification in generic mode.
  • +5 more suggestions
Loading version history...
Loading reviews...