Build a secure and reliable webhook receiver
Bergur DavidsenUpdated 2026-07-14
A webhook receiver is an internet-facing data ingestion endpoint. Keep its synchronous work small: authenticate, validate, persist or enqueue, and acknowledge.
Receiver flow
HTTPS request → authenticate → validate envelope → deduplicate → persist/enqueue → acknowledge quickly → process asynchronouslyDo not perform slow external calls or destructive business logic before acknowledging.
Authenticate first
Validate the released authentication header or secret configured for the webhook before parsing sensitive content deeply.
- Compare secrets safely.
- Reject missing or invalid credentials.
- Keep credentials in a secret manager.
- Support deliberate rotation.
- Never log authorization values.
- Restrict accepted methods and content types.
The x-usable-test-trigger header identifies tests but is not proof of authenticity.
Validate input
Enforce:
POSTand JSON content type;- bounded body size;
- recognized event type;
- valid workspace/resource IDs;
- envelope/data consistency;
- parsable timestamp;
- approved workspace allowlist;
- safe string and array limits.
Treat all payload fields as untrusted. Never interpolate titles, tags, or content into SQL, shell commands, file paths, or templates without safe handling.
Acknowledge quickly
Persist or enqueue the validated event, then return a 2xx response. Keep queue insertion durable enough that acknowledgment does not lose the event.
Return non-2xx only when redelivery could help or the request must be rejected. Avoid detailed responses that disclose receiver internals.
Make processing idempotent
Webhook delivery can be duplicated. If an explicit event ID is unavailable, derive a conservative key from documented stable fields such as event type, workspace/resource identity, and occurrence time, while retaining logic that can safely upsert the latest canonical state.
For side effects:
- use database uniqueness constraints;
- record completed operations;
- make notification sends deduplicated;
- make deletes/tombstones repeatable;
- check current state before writing.
Handle ordering
Events can arrive out of order. Prefer the newest canonical resource state for update-driven synchronization. For deletion, use a tombstone and ensure a late update cannot silently resurrect stale data.
When exact ordering is essential, serialize processing per fragment or workspace and compare version/timestamps where available.
Retry responsibly
Separate transient from permanent failures:
- retry timeouts, temporary upstream failures, and queue outages with bounded exponential backoff and jitter;
- do not retry invalid payloads or unauthorized requests unchanged;
- cap attempts;
- send terminal failures to a dead-letter or review queue;
- provide replay tooling that remains idempotent.
Do not assume Usable's sender retry policy; design the receiver to survive duplicates whether they come from sender retry, manual test, or operator replay.
Protect sensitive data
- Encrypt transport and approved storage.
- Minimize retained payload fields.
- Redact logs.
- Apply workspace-specific retention.
- Restrict queue and support access.
- Do not forward private content to third parties without approval.
- Delete data when the integration is retired.
Observability
Monitor request count, authentication failures, validation failures, acknowledgment latency, queue depth, processing success/failure, retry/dead-letter count, duplicate rate, and event age.
Use correlation data where documented, but do not turn credentials or full payloads into labels.
Rotation and incident response
- Generate a new dedicated receiver credential.
- Update the receiver to accept the transition safely.
- Update the webhook configuration.
- Trigger a test.
- Confirm successful authenticated delivery.
- Remove the old credential.
- Review recent failures and access logs.
After exposure, rotate immediately and assess delivered workspace data. Deleting a configuration does not invalidate a secret copied elsewhere.
Minimal pseudocode
receive request
require POST + application/json
verify receiver credential
parse bounded JSON
validate event type and workspace
if duplicate: acknowledge
persist event atomically
acknowledge 202
process queued event with bounded retries