Webhook integrations connect the systems that keep a business moving: a payment provider tells an application that an invoice changed, a CRM notifies a portal about a new lead, or a warehouse system reports that an order shipped. They are powerful because they let software react without waiting for a person to copy data between tools.
They are also easy to get subtly wrong. Providers retry deliveries, events can arrive out of order, network requests can time out after the receiver has already started work, and a valid-looking request may not be authentic. A reliable webhook integration treats every delivery as untrusted, repeatable input and gives the business a clear path to recover when processing fails.
This guide explains the architecture patterns that make webhooks dependable in Laravel, PHP, and other web applications.
Start With an Explicit Event Contract
Before writing a controller, document the event contract. Name the event, its version, the producer, the intended consumer, required fields, and the action the consumer may take. A useful event has a stable identifier, an event type, a creation timestamp, and the business resource identifier.
Keep the envelope separate from the payload. The envelope answers “what happened and when?” while the payload contains the data needed for this application’s action. This separation makes it possible to evolve a payload without changing how the receiver identifies and records an event.
Treat the provider’s documentation as the source of truth. Stripe, for example, documents that event order is not guaranteed and recommends tracking event IDs to identify duplicate deliveries. Your integration should not infer a sequence from arrival order or timestamps alone.
Version the contract deliberately. If a provider adds fields, tolerant parsing can usually accept them. If the meaning or type of an existing field changes, create a new handler or explicit version path rather than silently changing old business behavior.
Authenticate the Raw Request
A webhook endpoint is public by design, so authentication must happen before business logic. Use the provider’s signing scheme and verify the signature against the exact raw request body. Parsing JSON and then re-encoding it can change whitespace or character representation and invalidate a correct signature.
GitHub’s webhook guidance describes the common HMAC-SHA256 pattern: calculate a digest with the shared secret, compare it with the X-Hub-Signature-256 header, and use a constant-time comparison rather than a normal equality operator. Stripe similarly requires the raw body and its signature header for verification.
Store signing secrets outside source control, rotate them through a controlled process, and support a short overlap period when a provider allows two active secrets. Do not log the secret, the complete signature, or the full request body when it could contain customer data. Log a request identifier and a safe verification result instead.
Reject missing, malformed, or invalid signatures before creating jobs or changing records. Authentication should be a small, testable boundary that is easy to review independently from the rest of the handler.
Acknowledge Quickly, Process Asynchronously
A webhook sender usually expects a fast 2xx response. Stripe’s documentation recommends acknowledging a valid delivery before running complex work that could cause a timeout. The receiver should verify the request, record the event, enqueue a job, and return success. The worker can then perform the slower operation.
This split protects the endpoint from spikes. It also avoids a dangerous ambiguity: a provider may retry because your response timed out even though your application completed the update. If the event has not been durably recorded before the response, a retry can create duplicate work.
Use a queue with a visible status for each event. Useful states include received, queued, processing, completed, failed, and ignored. Keep the handler small and make the worker responsible for domain-specific work, retries, and notifications.
A queue is not a substitute for idempotency. It only moves the work to a better execution boundary.
Make Processing Idempotent
Assume the same event can arrive more than once. Store a provider event ID, source, and processing result in a unique table or equivalent durable store. Use a database uniqueness constraint so two workers cannot both claim a new event during a race.
When a duplicate arrives, return success after confirming that the original event is already completed or safely in progress. If the first attempt failed, keep enough information to retry it intentionally rather than creating a second business record.
Idempotency must also exist at the domain boundary. A “mark invoice paid” operation should be safe when called twice. A “create shipment” operation should use a stable external reference or an idempotency key so a retry cannot create two shipments.
Test the failure window explicitly: let the worker complete the business update, then simulate a process crash before it marks the event complete. A correct design will run again and produce the same final state.
Handle Ordering and Stale Events
Delivery order is not a business guarantee. An update event may arrive before a creation event, or an older update may be delayed until after a newer one. Do not let arrival order determine the final state unless the contract provides a reliable sequence number.
Where possible, fetch the current resource from the provider after validating the event. If the event says a payment changed, the provider’s current payment object may be safer than assuming the event contains every field your application needs.
For state synchronization, store the provider’s version, update timestamp, or sequence value and reject stale transitions. For workflows that are inherently ordered, model the state machine explicitly and record why a transition was accepted or deferred.
Retry With Limits and a Recovery Path
Retry only failures that are likely to recover, such as temporary network errors, rate limits, or a dependency outage. Do not repeatedly retry malformed payloads, authorization failures, or a business rule that will never pass.
Use exponential backoff with jitter and a maximum attempt count. Providers such as Amazon EventBridge document retry policies and dead-letter queues for events that cannot be delivered after retries. Your application should provide an equivalent operational path: preserve the failed event, expose the error reason, and give an authorized operator a safe replay action.
A replay should use the same idempotent handler as a normal delivery. Never ask an operator to edit production records manually just to force a webhook through. If the payload is invalid or the contract has changed, route it to a quarantine state for inspection instead.
Observe the Whole Delivery Lifecycle
A green endpoint response does not prove the business action succeeded. Track metrics and logs across verification, persistence, queueing, processing, and final side effects.
At minimum, record the provider, event type, event ID, received time, processing start and finish, outcome, retry count, and a correlation ID. Keep sensitive payload fields out of ordinary logs. Add alerts for verification failures, queue growth, repeated processing failures, and events approaching their retention limit.
Build a small replay and inspection screen for operators if webhooks are business-critical. It should show safe event metadata, the current state, the last error, and whether replay is allowed. Access should be role-based and every replay should be audited.
A Practical Webhook Checklist
Before enabling a production webhook, confirm that:
- The endpoint accepts only HTTPS POST requests.
- The raw request body is verified with the provider’s signature.
- Secrets are stored outside source control and can be rotated.
- Valid events are persisted before the response is returned.
- Work is processed asynchronously where it may be slow.
- Event IDs and domain operations are idempotent.
- Out-of-order and stale events have an explicit policy.
- Retries use limits, backoff, and a dead-letter or quarantine path.
- Operators can inspect failures and replay safely.
- Logs and metrics identify failures without exposing sensitive data.
Reliable webhooks are less about a clever controller and more about durable boundaries. When authentication, persistence, idempotency, queues, retries, and observability are designed together, external events become a dependable part of the product instead of a source of mysterious duplicate records.
QorLogics builds custom API integrations and business automation systems around real workflows. For a related API design perspective, read our guide to RESTful API best practices, or contact the team to discuss an integration.