Prompt
How do I receive inbound webhooks, validate them, and process them asynchronously?
Latest observation
A solid pattern is:
- Expose a webhook endpoint
- Verify the request is authentic
- Return fast
- Hand off work to a background queue
- Process idempotently in a worker
Recommended flow
1) Receive the webhook
Your HTTP endpoint should do as little as possible:
- parse headers/body
- verify signature/authentication
- deduplicate if needed
- enqueue a job
- return
200 OKor202 Acceptedquickly
This keeps the sender from timing out.
2) Validate the webhook
Validation usually includes:
- Signature verification using a shared secret or public key
- Timestamp check to prevent replay attacks
- Payload schema validation to ensure expected shape
- Source allowlisting if provider IPs are stable
- Idempotency / event ID deduplication to avoid double processing
Common signature schemes:
- HMAC over the raw request body
timestamp + bodysigned with HMAC- RSA/ECDSA signatures over the payload
Important: verify against the raw body, not a re-serialized JSON object.
3) Process asynchronously
After validation, publish the event to a queue or job system:
- SQS
- RabbitMQ
- Kafka
- Redis Queue / BullMQ
- Celery / Sidekiq / Hangfire / etc.
Then have a worker:
- consume the event
- perform business logic
- retry on transient failures
- dead-letter permanently failing messages
Example architecture
Webhook sender → Your API → Queue → Worker → Database / downstream systems
This gives you:
- fast HTTP responses
- retries
- resilience
- scaling independently for ingest and processing
Example pseudocode
POST /webhooks/provider
raw_body = request.raw_body
signature = request.header["X-Signature"]
timestamp = request.header["X-Timestamp"]
if not is_valid_timestamp(timestamp):
return 400
if not verify_hmac(raw_body, signature, secret):
return 401
event = parse_json(raw_body)
if already_processed(event.id):
return 200
enqueue("webhook-events", event)
return 202
Worker:
while true:
event = dequeue("webhook-events")
try:
process_event(event)
mark_processed(event.id)
except transient_error:
retry_later(event)
except permanent_error:
send_to_dead_letter(event)
Best practices
Make processing idempotent
Webhook providers often retry delivery. Your worker should safely handle the same event multiple times.
Typical approaches:
- store
event_idin a dedupe table with a unique constraint - make state transitions conditional
- use upserts instead of inserts
Don’t trust the payload blindly
Even after signature verification:
- validate fields and types
- reject malformed data
- enforce expected event types
Preserve the raw payload
Store the original webhook payload and headers for debugging and audit.
Respond quickly
Aim to respond within a few hundred milliseconds if possible.
Separate “ingest” from “process”
The HTTP endpoint should only ingest and queue. Business logic belongs in workers.
Use retries thoughtfully
- Retry transient errors with exponential backoff
- Don’t retry invalid payloads
- Send poison messages to a dead-letter queue
Minimal checklist
- HTTPS only
- Signature verification
- Timestamp/replay protection
- Schema validation
- Dedupe/idempotency
- Queue for async processing
- Worker with retries + DLQ
- Logging and metrics
- Store raw event for audit/debugging
If you want, I can show this in a specific stack like Node.js/Express, Python/FastAPI, Go, or C#/.NET.