The API Guys
Diagram illustrating the difference between webhook push notifications and polling request cycles in API integrations
·6 min read·The API Guys

Webhooks vs Polling - Choosing the Right Integration Pattern

APIWeb DevelopmentLaravelIntegration

When you are building a system that needs to react to events in an external service - a payment being completed, an order being shipped, a form being submitted - you face a fundamental design choice. Do you ask the external service for updates on a regular basis, or do you wait for it to tell you when something happens?

These two approaches are known as polling and webhooks. Both are widely used, both have their place, and choosing the wrong one for a given situation can result in systems that are slow to react, expensive to run, or difficult to maintain. This post walks through how each pattern works, the trade-offs involved, and how we approach the decision at The API Guys.

How Polling Works

Polling is the simpler of the two patterns to understand. Your application periodically sends a request to an external API asking whether anything has changed since the last check. If the answer is yes, you process the updates. If the answer is no, you wait and try again later.

In a Laravel application, this typically looks like a scheduled job running every few minutes via the task scheduler:

Schedule::job(new CheckOrderStatusJob)->everyFiveMinutes();

The job fires a request to the external service, compares the response to what you already know, and handles any differences. Simple, predictable, and entirely within your control.

The drawbacks become apparent quickly. If your interval is five minutes, you will always be up to five minutes behind reality. If you shorten the interval to compensate, you increase the number of API requests you are making - potentially running into rate limits and certainly increasing your infrastructure costs. If nothing has changed, every one of those requests is wasted effort on both sides.

How Webhooks Work

Webhooks invert the model entirely. Rather than your application asking for updates, you register a URL with the external service and it sends a request to that URL whenever something relevant happens. Your application receives the event, processes it, and responds with a success status.

This is a push model rather than a pull model. You are not asking - you are being told.

In practice, a webhook endpoint in a Laravel application looks something like this:

Route::post('/webhooks/payments', [PaymentWebhookController::class, 'handle']);

The external service calls that URL the moment a payment is completed. You process it immediately, with no delay and no wasted requests in between.

The trade-off is that you are now dependent on the external service to reliably deliver those events. If their delivery system has an outage, or if your endpoint is temporarily unavailable, events can be missed. Webhooks require careful handling: you need to verify that incoming requests are genuinely from the expected sender, queue the processing to avoid timeout failures, and implement logic to handle duplicate or out-of-order deliveries.

Verifying Webhook Authenticity

One of the most important - and most commonly overlooked - aspects of webhook implementation is signature verification. Because your webhook endpoint is publicly accessible, anyone can send requests to it. A well-implemented webhook provider will include a signature with each request, typically as an X-Signature or similar header, derived from a shared secret and the request body.

Always verify this signature before processing the payload:

$signature = hash_hmac('sha256', $request->getContent(), config('services.provider.secret'));

if (!hash_equals($signature, $request->header('X-Webhook-Signature'))) {
    abort(403);
}

Skipping this step means your webhook handler will process requests from anyone who discovers the URL - a significant security risk.

Handling Webhooks Reliably

A webhook endpoint should do as little as possible during the request itself. The external service is waiting for your response, and if your processing takes too long, it may consider the delivery failed and retry - potentially causing duplicate processing. The correct pattern is to acknowledge receipt immediately and dispatch the real work to a queued job:

public function handle(Request $request): Response
{
    // Verify signature first
    $this->verifySignature($request);

    // Dispatch to queue immediately
    ProcessPaymentWebhook::dispatch($request->all());

    // Acknowledge receipt
    return response()->noContent();
}

This keeps your response time well within the provider's timeout window and shifts the actual processing to your queue workers, where it can be retried independently if something goes wrong.

When to Use Polling

Polling is the right choice when the external service does not support webhooks - which is more common than you might expect, particularly with older or enterprise systems like certain ERP and accounting platforms. If the service only exposes a REST API with no event notification mechanism, polling is your only option.

Polling also makes sense when the data you need is inherently time-based rather than event-based. If you are generating a daily report from an external system, a scheduled job that runs once a day is entirely appropriate. There is no event to react to - you simply need the data at a specific time.

Finally, polling can be preferable when you need a simple audit trail of what your application knew and when it knew it. Every poll creates a clear record. Webhook delivery, by contrast, can be difficult to reconstruct if something goes wrong.

When to Use Webhooks

Webhooks are the right choice when you need to react to events quickly and the external service supports them. Payment confirmations, order status changes, form submissions, repository pushes - any situation where minutes of delay is unacceptable and the volume of events is unpredictable.

They are also significantly more efficient at scale. A system handling thousands of customers, each with orders that might update at any moment, would require an enormous number of polling requests to stay current. Webhooks deliver only the events that actually occur, which is far more cost-effective.

For integrations with modern platforms - Shopify, Stripe, GitHub, and most well-designed SaaS tools - webhooks are almost always the preferred approach. These platforms have invested heavily in reliable event delivery and will often give you tooling to replay missed events, inspect delivery history, and manage subscriptions.

A Hybrid Approach

In production systems, we frequently use both patterns together. Webhooks handle real-time event delivery for the common case. A background polling job runs less frequently - perhaps every hour - as a reconciliation mechanism to catch any events that were missed due to downtime or delivery failures. This gives you the responsiveness of webhooks with the reliability of polling as a safety net.

This is a pattern we use regularly when integrating Laravel applications with platforms like Shopify or Business Central, where the cost of a missed event can be significant and a belt-and-braces approach is warranted.

Practical Considerations for Laravel

Laravel's queue system makes both patterns straightforward to implement well. For polling, the task scheduler handles timing. For webhooks, the combination of a lightweight controller, signature verification, and queued jobs covers the vast majority of use cases. Packages such as Spatie's Laravel Webhook Client can accelerate implementation by handling signature verification, logging, and processing out of the box.

If you are building or maintaining integrations and are unsure which pattern fits your situation, or if you need help designing a reliable event-driven architecture, get in touch with us. It is one of the more nuanced decisions in API integration work, and getting it right early saves a significant amount of rework later.

Ready to Start Your Project?

Get in touch with our Leeds-based team to discuss your Laravel or API development needs.