typescriptintermediate
Webhook Handler with Signature Verification
Process incoming webhooks with HMAC signature verification, replay protection, and idempotency.
typescriptPress ⌘/Ctrl + Shift + C to copy
import crypto from 'crypto';
import type { IncomingMessage, ServerResponse } from 'http';
interface WebhookConfig {
secret: string;
headerName?: string;
tolerance?: number; // seconds
}
class WebhookHandler {
private processedIds = new Set<string>();
constructor(private config: WebhookConfig) {}
// Verify HMAC signature (e.g., Stripe, GitHub style)
verify(payload: string | Buffer, signature: string): boolean {
const expected = crypto
.createHmac('sha256', this.config.secret)
.update(payload)
.digest('hex');
const sig = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(sig, 'hex'),
Buffer.from(expected, 'hex'),
);
}
// Check timestamp to prevent replay attacks
checkTimestamp(timestamp: number): boolean {
const tolerance = this.config.tolerance ?? 300;
const age = Math.abs(Date.now() / 1000 - timestamp);
return age <= tolerance;
}
// Idempotency check
isProcessed(eventId: string): boolean {
if (this.processedIds.has(eventId)) return true;
this.processedIds.add(eventId);
// Cleanup old IDs periodically in production
if (this.processedIds.size > 10000) {
const arr = [...this.processedIds];
this.processedIds = new Set(arr.slice(-5000));
}
return false;
}
// Express middleware
middleware() {
return async (req: IncomingMessage, res: ServerResponse) => {
const headerName = this.config.headerName ?? 'x-hub-signature-256';
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk as Buffer);
const body = Buffer.concat(chunks);
const signature = (req.headers as any)[headerName] as string;
if (!signature || !this.verify(body, signature)) {
res.writeHead(401);
res.end(JSON.stringify({ error: 'Invalid signature' }));
return;
}
const payload = JSON.parse(body.toString());
// Idempotency
const eventId = (req.headers as any)['x-event-id'] as string;
if (eventId && this.isProcessed(eventId)) {
res.writeHead(200);
res.end(JSON.stringify({ status: 'already_processed' }));
return;
}
// Process webhook
console.log('Webhook received:', payload);
res.writeHead(200);
res.end(JSON.stringify({ status: 'ok' }));
};
}
}
// Usage
const handler = new WebhookHandler({ secret: process.env.WEBHOOK_SECRET ?? 'secret' });
// Test verification
const payload = JSON.stringify({ event: 'user.created', data: { id: '123' } });
const sig = crypto.createHmac('sha256', 'secret').update(payload).digest('hex');
console.log('Valid:', handler.verify(payload, sig));Use Cases
- GitHub/Stripe webhook processing
- Secure event ingestion
- Third-party integration endpoints
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptintermediate
Node.js Token Bucket Rate Limiter
Implement an in-memory token bucket rate limiter for controlling API request throughput.
Best for: Protecting APIs from abuse and DDoS
#nodejs#rate-limiting
typescriptintermediate
Node.js Crypto Utility Functions
Common cryptographic operations: hashing, HMAC, encryption, random tokens, and password hashing.
Best for: Secure password storage and verification
#nodejs#crypto
typescriptintermediate
Request Validation Schema Builder
Build a lightweight request validation layer with type inference for API endpoints.
Best for: API request body validation
#nodejs#validation
typescriptbeginner
Native HTTP Server
Create a lightweight HTTP server using Node.js built-in http module with routing and JSON responses.
Best for: Lightweight API server without frameworks
#nodejs#http