Webhooks

Webhook vs Polling: Which Should You Use?

Both approaches solve the same problem. But one is a lot more annoying — and it's not always the one you'd expect.

April 20257 min read

The Core Question

You want to know when something happens on another system. Maybe a payment completes, a file finishes processing, or a user signs up on a third-party platform. The question is: how do you find out?

You have two basic options. You can ask repeatedly until you get the answer you're waiting for — that's polling. Or you can leave your number and wait for a call back — that's webhooks.

Neither is universally better. Each has situations where it's the right tool. Let's look at both honestly.

Polling: The "Are We There Yet?" Approach

With polling, your application hits an API endpoint on a schedule — every 5 seconds, every minute, every hour — and checks whether anything changed.

// Polling every 5 seconds
setInterval(async () => {
  const response = await fetch('/api/order/123/status');
  const { status } = await response.json();

  if (status === 'shipped') {
    console.log('Order shipped! Stop polling.');
    clearInterval(timer);
  }
}, 5000);

It works. It's simple to implement. You don't need any infrastructure — just a timer and an API call. But it has a fundamental problem: most of those API calls come back with "nothing changed."

The real cost of polling

Say you're waiting for a payment to complete. Payments usually take under 3 seconds, but you're polling every second just in case.

100 users waiting

= 100 API calls per second, continuously, whether anything changed or not

Average wait: 3 seconds

= ~297 wasted API calls per user, per transaction

At scale

= significant server load, API rate limit headaches, and higher infrastructure costs

The other issue is latency. If you're polling every 30 seconds and the event happens 1 second after your last poll, you won't find out for 29 seconds. Polling frequently reduces latency but increases load — it's a tension you're constantly fighting.

Webhooks: The "Don't Call Us, We'll Call You" Approach

With webhooks, you flip the model. Instead of you checking for updates, you give the other service a URL and say: "When something happens, POST to this URL."

// Your server just waits for events to arrive
app.post('/webhooks/payments', (req, res) => {
  const { type, data } = req.body;

  if (type === 'payment.succeeded') {
    fulfillOrder(data.object.orderId);
  }

  res.sendStatus(200); // Always respond quickly
});

No polling loop. No wasted requests. Your server just sits there until a real event arrives, then handles it. The result: near-instant notification and zero unnecessary API calls.

The trade-off

Webhooks require your server to be publicly accessible and always online. If your server is down when an event fires, you might miss it (though most services retry). You also need to handle things like signature verification, duplicate events, and the fact that you can't "request" data — you can only receive it when they push it.

Head-to-Head Comparison

FactorPollingWebhooks
ImplementationSimple — just a loop and an API callRequires an endpoint on your server
LatencyDepends on poll interval (seconds to minutes)Near-instant (milliseconds after the event)
Server loadHigh — many empty requests at scaleLow — only fires when something happens
ReliabilityYou control the retry logic entirelyDepends on the sender's retry policy
Works offlineYes — you poll when you're readyNo — server must be reachable
Local devEasy — just run the loop locallyNeeds a tunnel (ngrok, etc.)
Cost at scaleExpensive — API rate limits, server loadCheap — only pay for real events

When to Use Each

If the service supports webhooks, use them. That's the short answer. You get instant notification, zero wasted requests, and your server isn't doing unnecessary work. The setup is a bit more involved than a polling loop, but you do it once and it just runs. Stripe's webhook best practices doc is a good read on handling retries and idempotency once you're past the basics. GitHub's webhook documentation is another solid reference if you're working with repository events.

Polling makes sense when the other service doesn't offer webhooks — which still happens more often than you'd expect with older APIs. It's also the right call when you need to fetch data on demand rather than react to events, or when your server isn't always running. A cron job polling once a minute is perfectly fine for a lot of use cases. Just don't poll every second unless you genuinely need it. If you're looking for a middle ground, long polling is worth understanding — it's a technique that keeps a connection open until the server has something to say, giving you near-real-time updates without a full webhook setup.

A lot of production systems actually use both together. Webhooks handle the real-time updates. A scheduled job runs every hour or so as a reconciliation pass — catching anything that slipped through if the server was briefly down or a webhook delivery failed. You get the speed of webhooks with polling as a quiet safety net in the background. (If you need true bidirectional real-time communication, that's a different problem — look at WebSockets or Server-Sent Events instead.)

How They Work Together in Practice

Let's make this concrete. Say you're building an e-commerce platform and integrating with a payment processor.

The naive approach: poll the payment status every second until it resolves. You've seen why that's wasteful. The right approach: a webhook. The payment processor calls your /webhooks/payments endpoint the moment the result is known — usually within a few hundred milliseconds. Your server fulfills the order immediately without ever having to ask.

But here's where production reality gets interesting. What if your server was briefly down when the payment completed? What if a network issue caused the webhook to fail after 3 retries? Most payment processors will retry failed webhooks, but retry windows can be hours apart — not ideal when a customer is waiting for their order confirmation.

The solution most teams land on: a reconciliation job. A cron that runs every 5 or 10 minutes, checks for any orders stuck in a "payment pending" state longer than expected, and proactively calls the API to fetch the latest status. Not real-time, but a quiet safety net.

The pattern in brief

Webhook handles 99% of cases instantly. Polling reconciliation catches the 1% that fall through. You get speed and reliability without betting everything on a single delivery mechanism.

The Bottom Line

If the service you're integrating with supports webhooks, use them. The lower latency and reduced load are worth the slightly more complex setup.

If webhooks aren't an option, polling is completely fine — just be thoughtful about your poll interval. Polling every second is rarely necessary. Polling every 30 seconds or every minute is usually enough, and it's much kinder to everyone's infrastructure.

And if you're building something where reliability really matters, consider combining both: webhooks for speed, polling as a fallback.

Keep Reading