Webhooks from payment gateways, ERPs, and CRMs are inherently asynchronous, out-of-order, and delivered at-least-once. Learn how to architect idempotent consumer workers using PostgreSQL transactions and distributed locks.
The Illusion of Ordered Webhook Streams
Most engineering teams build their first webhook endpoint under an intuitive but fatal assumption: that events arrive sequentially and exactly once. A customer initiates a checkout, the billing provider fires checkout.completed, and subsequent subscription lifecycle events follow in orderly succession.
In production, this assumption can create incorrect state. Real-world networks experience packet retransmission, regional gateway jitter, and concurrent retry storms. When a provider fires an event, network lag can cause a subsequent subscription.canceled event to arrive and execute before the original subscription.created payload reaches your server. If a database blindly upserts the latest payload, the record can reflect an incorrect subscription state.
The Anatomy of Naive Failures
Consider what happens when a billing provider re-transmits an event due to a temporary TCP reset. If your handler directly triggers inventory reservation or sends an invoice:
- Double Fulfillment: A single customer purchase results in duplicate inventory depletion or multiple automated fulfillment tickets.
- Lock Contention & Race Conditions: Concurrent worker processes attempt to update the same customer row simultaneously, resulting in deadlock timeouts (PostgreSQL error
40P01) or lost updates. - Timeout Cascades: Heavy synchronous operations inside the HTTP webhook handler cause response latency to exceed provider thresholds (often 5 to 10 seconds), prompting the provider to flag the endpoint as failing and retry with exponential volume.
Architecture Pattern: The Three-Stage Ingestion Pipeline
For consequential workloads, consider separating webhook ingestion from business processing with a staged architecture:
1. Fast Ingestion and Signature Verification (< 150ms)
The HTTP handler performs exactly three operations before returning HTTP 200 OK:
- Validates the cryptographic HMAC signature using the shared webhook secret.
- Generates a deterministic hash of the payload or extracts the provider's unique
event_id. - Inserts the raw payload into an append-only
webhook_eventsqueue table with statuspending.
CREATE TABLE webhook_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider VARCHAR(64) NOT NULL,
event_id VARCHAR(128) NOT NULL,
event_type VARCHAR(128) NOT NULL,
payload JSONB NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(32) NOT NULL DEFAULT 'pending',
processed_at TIMESTAMPTZ,
error_message TEXT,
CONSTRAINT uq_provider_event UNIQUE (provider, event_id)
);
2. Idempotent Asynchronous Processing
A background queue worker (such as Cloudflare Queues, SQS, or a Redis-backed worker) pulls pending events. Before applying any mutation, the worker checks:
- Has this event already been processed? Checked via the unique index on
(provider, event_id). - Is this event older than current record state? For lifecycle updates, compare the event's
created_attimestamp against the entity'sversionorlast_event_timestampin your database. If the event is older, mark it asignored_staleand exit cleanly.
3. Transactional Outbox for Side Effects
If processing the webhook requires triggering external side effects (e.g., sending an email via Resend or notifying an ERP), never call the third-party API directly inside the database transaction. If the transaction rolls back, the email was still sent. Instead, write the outgoing action into an outbox_messages table within the same ACID transaction, and let an outbox runner dispatch it safely.
Production Checklist for Webhook Reliability
- Consider retaining raw payloads: Subject to privacy and retention requirements, raw payloads can support investigation and replay when third-party schemas change or a regression occurs.
- Implement dead-letter queues (DLQ): Payloads that fail after 5 exponential retries must be routed to a dead-letter queue with alerting for engineering review.
- Enforce rate-limiting and IP allowlisting: Protect your webhook ingress from DDoS attacks by verifying IP ranges where supported and caching signature verification keys.
From analysis to implementation
Is this failure mode showing up in your system?
Bring the current architecture, operational constraint, and consequence of failure. Conflect can help determine what is worth changing—and what is not.
Continue reading
Other decisions from the engineering practice.
Systems Architecture
When Business Systems Disagree: A Decision Guide for Reliable Integrations
How to define ownership, choose a proportionate integration approach, and make data mismatches visible before they become operational work.
Read the analysisSystems Architecture
Build vs. Buy: When Off-the-Shelf SaaS Becomes an Operational Bottleneck
An objective engineering and financial framework for identifying when custom systems outperform packaged software.
Read the analysisAI Engineering
Designing Deterministic Guardrails for Enterprise AI Workflows
How to safely harness probabilistic foundation models inside mission-critical operational systems.
Read the analysis