A Practical Introduction to Webhooks
Build webhook receivers that authenticate deliveries, persist accepted events, handle duplicates, and expose failures instead of trusting a successful response.
By FindTheWeb Editorial

When a payment provider confirms a transaction after your checkout page has closed, your server still needs to learn about it. A webhook lets the provider send an HTTP request to an endpoint you control when an event occurs. The useful part is the notification; the difficult part is handling it safely when delivery is late, repeated, or interrupted.
Understand the sender and receiver
A webhook has an event producer, a subscription describing which events you want, and a receiver URL. The producer sends an event payload, commonly JSON, to that URL. Your handler authenticates the delivery, interprets the supported event, and records or schedules the corresponding work.
This differs from polling, where your application repeatedly asks another service whether something changed. Neither approach is universally better. Webhooks can reduce repeated checks, while polling or reconciliation can help recover missed notifications. A robust integration may use both for different responsibilities.
Read the producer's contract before writing the handler: authentication method, event identifier, payload version, timeout, retry policy, and ordering guarantees. GitHub's webhook best practices describe its own expectations, including HTTPS, secrets, event filtering, and delivery identifiers. Those details are an example, not a universal webhook standard.
Authenticate the delivery before trusting it
A public endpoint can receive requests from anyone. A plausible event name and JSON structure do not establish who sent them. Use the provider's documented signature-validation method and its maintained library where available. Keep the signing secret on the server, separate from source code and browser-delivered configuration.
Signature schemes often depend on the exact request bytes. Stripe's webhook documentation requires its raw request body for signature verification. Parsing and reserializing JSON before verification can change those bytes. Configure the route's body handling deliberately rather than inheriting an application-wide parser without checking compatibility.
Use HTTPS and keep certificate verification enabled. Avoid putting secrets in the receiver URL, because URLs commonly appear in logs and dashboards. If the provider supports signed timestamps or replay controls, follow its documented tolerance and clock requirements. A valid signature still does not make an old delivery a new business event.
Acknowledge only what you have safely accepted
Separate receiving a delivery from completing the business action. A practical handler validates the request, stores the accepted event durably, and responds promptly. A worker can perform slower work afterward. The durable store might be a database-backed inbox or a queue with suitable persistence, depending on the application.
Do not return success before the event is safely recorded. If the process dies between the response and storage, the sender may believe delivery succeeded while your work disappears. Conversely, doing every slow external call before responding can exceed the sender's deadline and encourage repeated deliveries.
GitHub specifies a ten-second response expectation for its deliveries. Other services have their own limits. Budget verification and storage accordingly, and monitor the acceptance path separately from worker execution. A fast endpoint with a stalled worker is not a healthy integration.
- Receive the request over HTTPS with required headers and raw bytes.
- Validate the signature and supported event schema.
- Store the event identifier and payload durably.
- Acknowledge acceptance, then process through a worker.
- Record the outcome and reconcile retries or ambiguous external effects.
Expect duplicates and uncertain order
Choose a stable event identifier for deduplication. Enforce uniqueness in durable storage rather than relying on an in-memory set that disappears on restart. Keep the event's business object identifier as well, because two different events can concern the same order or subscription.
Processing should be idempotent: applying the same intended operation again should not create another unintended effect. Updating an order to a known paid state is different from blindly incrementing its payment count. For an external action, use that service's idempotency facility where available and keep the identifier that links your action to the event.
Do not assume events arrive in business order. Stripe explicitly documents duplicate deliveries and lack of ordering guarantees. When an older event arrives after a newer one, compare the relevant state or fetch the current authoritative object rather than overwriting it with stale information. The correct rule depends on the event model.
Hypothetical example: sending a receipt
Imagine a hypothetical training company receiving a successful-payment event. It stores the event ID with a unique constraint, associates the payment with a booking, and schedules a receipt. The provider retries the same event after a connection failure. The duplicate is recognized rather than creating a second booking.
The email provider then times out. The team records an unresolved notification outcome and checks the provider before resending where its API permits. Deduplicating the payment event alone does not guarantee that a separate email side effect happens exactly once. Each boundary needs its own handling.
A later reconciliation compares paid bookings with authoritative payment records and identifies any booking still missing its expected state. This is a proposed design, not a reported production test. Its purpose is to make missing and ambiguous work visible rather than hiding everything behind a successful HTTP response.
Webhook readiness checklist
- Are subscriptions limited to event types the receiver understands?
- Is signature verification performed before business processing?
- Does the body parser preserve what the provider's signature requires?
- Is accepted work durable before success is returned?
- Can duplicate and out-of-order events be handled safely?
- Are failed jobs visible with a controlled redelivery path?
- Do logs omit secrets and unnecessary personal payload data?
Exercise invalid signatures, duplicates, worker interruption, and a missed response in a test environment using synthetic events. Keep test and live signing secrets distinct. Before launch, assign someone to watch overdue work and explain how reconciliation repairs gaps. A webhook is a delivery mechanism, not a guarantee that the entire business process completed.