Why webhook receivers fail in production
Handling outbound email is straightforward: you call a REST API and get an ID. Handling inbound webhook events — deliveries, opens, clicks, bounces, and spam complaints — is where production email systems actually break.
Receivers fail for three predictable reasons: unverified endpoints accepting forged payloads, long-running database operations causing HTTP timeouts, and missing Dead-Letter Queues (DLQs) causing permanent data loss during downstream outages.
1. Verifying HMAC-SHA256 signatures in constant time
Never process an email webhook without cryptographically verifying its origin. An unauthenticated endpoint allows any attacker to forge bounce events and poison your suppression database.
SadaSend signs every delivery event with HMAC-SHA256 using your endpoint secret. Always verify the signature using constant-time comparison to prevent timing attacks.
import crypto from 'node:crypto';
export function verifyWebhookSignature(
rawPayload: string,
signatureHeader: string,
secret: string
): boolean {
// Header format: t=1726780000,v1=6a2b3c...
const parts = Object.fromEntries(
signatureHeader.split(',').map((p) => p.split('='))
);
const timestamp = parts['t'];
const expectedSignature = parts['v1'];
if (!timestamp || !expectedSignature) return false;
// Reject timestamps older than 5 minutes to prevent replay attacks
const ageSeconds = Math.floor(Date.now() / 1000) - Number(timestamp);
if (Math.abs(ageSeconds) > 300) return false;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(`${timestamp}.${rawPayload}`);
const computed = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf-8'),
Buffer.from(expectedSignature, 'utf-8')
);
}2. Decouple ingestion from processing
The biggest mistake in webhook handlers is doing heavy work (database writes, third-party API calls, CRM updates) inside the HTTP request handler.
Email providers timeout requests after 5 to 10 seconds. If your database has a slow query spike, your receiver times out, the provider treats it as a failure, and initiates an avalanche of retries. Ingest immediately to Redis or BullMQ, return HTTP 200/202, and process asynchronously.
3. Surviving failures: Dead-Letter Queues (DLQs)
When a webhook fails 10 consecutive retries (due to a bug in your worker code or a database schema mismatch), the provider drops the event unless there is a Dead-Letter Queue.
SadaSend preserves failed deliveries in an inspectable Dead-Letter Queue, capturing the raw payload, HTTP status, and traceback, allowing you to fix your code and replay failed deliveries with one click.
Building AI agents that send email?
Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.