Karya Semi
HomeBlogSearchCategoriesAboutContact
Karya Semi

Less noise. More notes.

HomeBlogAboutContactPrivacy PolicyDisclaimer

© 2026 Karya Semi. All rights reserved.

XGitHubLinkedIn
  1. Home
  2. /Categories
  3. /Web Development

How to Design a Reliable Webhook Receiver

A practical guide to webhook receivers: signature checks, idempotency, retries, ordering, persistence, observability, and tests.

Dian Rijal Asyrof/July 20, 2026/5 min read
Illustration for How to Design a Reliable Webhook Receiver

Webhook receivers sit at the edge of your application and someone else's system. They process events you do not control, on a schedule you do not choose, with retries that may arrive after you thought the work was finished. is easy to discuss in slogans and harder to operate in production. The useful questions are smaller: what breaks, what gets measured, and what can a team recover without improvising at the worst possible time? This guide focuses on those questions.

A good implementation does not try to eliminate every failure. It makes failure visible, limits the damage, and gives the next person a clear path back to a working system.

Accept first, process second

Return a fast response only after the request has passed basic validation and has been written to durable storage. Do not make the sender wait for a slow email, payment update, or media job.

A small event table can hold the provider name, event identifier, received time, payload hash, processing state, attempt count, and last error. The table becomes the record that lets a worker retry safely.

Verify the sender

Use the provider's signature scheme and verify it against the raw request body. Parsing JSON before checking the signature can change whitespace or encoding and make verification unreliable.

Keep secrets outside source control. Rotate them through configuration and make a failed verification visible in logs without printing the signature, token, or full payload.

Make handlers idempotent

Providers retry for ordinary reasons: a timeout, a network interruption, or a response they did not receive. Your handler must treat the same event arriving twice as normal.

Store the provider event ID under a unique constraint. If the ID already exists, acknowledge the request and skip the side effect. For operations without a provider ID, derive a stable key from fields that identify the business event.

Handle retries and ordering

A queue with bounded retries is usually enough for a small application. Add backoff, a maximum attempt count, and a dead-letter state that someone can inspect.

Events can arrive out of order. Use provider timestamps or sequence numbers when available, but do not assume a timestamp alone gives a safe ordering guarantee. If the business rule needs the latest state, fetch the current resource before applying an old event.

Test the unpleasant cases

Test duplicate delivery, invalid signatures, malformed JSON, slow downstream services, worker crashes, and a retry that arrives after a successful side effect. These cases are the real contract.

Keep a small fixture set from production-shaped payloads with private data removed. Run it in CI and use it when upgrading the SDK or changing the event schema.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

Write the failure case down before writing the happy path. This forces the design to name its assumptions. It also gives reviewers something specific to challenge instead of asking whether the system feels safe.

Small teams should prefer boring controls that are easy to inspect. A short log, a durable record, and one tested recovery command often beat a large platform that nobody owns.

The best signal is usually close to the user-visible outcome. Track whether work was accepted, completed, retried, or abandoned. Internal counters help, but they should explain the outcome rather than replace it.

Run the procedure in a test environment and then repeat it during a quiet production window. Documentation that has never been exercised is a guess with formatting.

DR

Dian Rijal Asyrof

Writes about useful AI tools, programming practice, and the craft of building reliable software.

Previous articleMicrosoft’s July 2026 Patch Tuesday Shows the New Cost of AI-Found VulnerabilitiesNext articleChoosing a Database Migration Strategy for Production Teams
WebhooksBackendApi
On this page↓
  1. Accept first, process second
  2. Verify the sender
  3. Make handlers idempotent
  4. Handle retries and ordering
  5. Test the unpleasant cases

On this page

  1. Accept first, process second
  2. Verify the sender
  3. Make handlers idempotent
  4. Handle retries and ordering
  5. Test the unpleasant cases

See also

Illustration for Understanding Database Locks: Row, Page, and Table Level Mechanics
Software Engineering/Jul 20, 2026

Understanding Database Locks: Row, Page, and Table Level Mechanics

A practical breakdown of database locking mechanisms, isolation levels, and lock escalation to prevent deadlocks and performance degradation in production.

5 min read
DatabaseBackend
Illustration for Postgres Transactions Are a Hidden Superpower for Distributed Systems
Software Engineering/Jul 3, 2026

Postgres Transactions Are a Hidden Superpower for Distributed Systems

Before reaching for Kafka, Redis, or a dedicated message queue, consider what Postgres can already do. The database most teams already use has surprisingly powerful coordination primitives built in.

3 min read
PostgresDatabase