Webhooks vs Polling: Choosing a Pattern
Every integration between two systems eventually answers one question: how does system B find out that something happened in system A? There are exactly two families of answer. Either B keeps asking ("anything new? anything new? anything new?") — that's polling — or A promises to tell B when something happens — that's a webhook. Everything else in integration design is detail layered on top of this choice.
The internet's conventional wisdom is that webhooks are modern and polling is embarrassing, which is roughly as accurate as most conventional wisdom. Polling is simple, robust, and self-healing. Webhooks are fast, efficient, and quietly treacherous in ways that only reveal themselves in production. Mature integrations — the ones inside payment processors and e-commerce platforms — almost always use both. Let's build up to why.
Polling: the pattern everyone underestimates
Polling means running a scheduled job that calls the other system's API: "give me everything that changed since my last check." Every minute, every five minutes, every hour — whatever the freshness requirement allows.
Why polling is better than its reputation
- You control the entire loop. No inbound endpoint to build, secure, or debug. The other system needs zero knowledge of you. If your site is behind a firewall, on a local network, or on a host that makes inbound requests awkward, polling just works.
- It's self-healing by construction. If your site is down for an hour, the next poll picks up everything that happened in the gap — provided you poll by "changes since timestamp X" rather than "latest N items." Missed data is impossible in a correctly written poller. This property is worth more than it sounds; hold that thought.
- It's trivially debuggable. A poll is a request you can run by hand, in a terminal, right now. "Why didn't the order sync?" becomes "run the poll query and look."
Where polling hurts
- Latency is the polling interval. Poll every 5 minutes and your average delay is 2.5 minutes, worst case 5. For a nightly report sync, irrelevant. For "payment received, release the download," unacceptable.
- It's almost all waste. If 10 events happen per day and you poll every minute, about 1,430 of your 1,440 daily requests return nothing. That's rate-limit budget and server load spent on the word "no."
- The tempting fix makes it worse. Want lower latency? Poll faster — and now you're burning quota faster and flirting with 429s. Polling frequency and rate limits are in permanent tension.
One implementation note that separates good pollers from data-losing ones: use a cursor, not a clock you trust. Store the timestamp or ID of the last item you processed and always ask for "everything after my cursor." Never ask for "the last 10 items" (a burst of 11 loses one forever) and never assume the previous poll ran (crons get skipped, servers reboot).
Webhooks: the pattern everyone under-engineers
A webhook inverts the arrow: you register a URL with the other system, and when an event happens — order placed, payment settled, contact unsubscribed — it sends an HTTP POST to your URL with the event details. Latency drops from "polling interval" to "seconds." Wasted requests drop to zero; every message means something.
That's the brochure. Now the production realities, each of which is a class of incident waiting for the unprepared:
1. Delivery is not guaranteed — and you must answer fast
What happens if your site is down, slow, or mid-deploy when the webhook arrives? Decent providers retry with backoff for hours or days; sloppy ones try once and shrug. Either way, a webhook consumer that's ever offline will eventually miss events, and unlike polling, a missed webhook leaves no trace — there is no "gap" to notice, just an order that never appeared.
Related: most providers count a delivery as failed unless you respond quickly (often within a few seconds) with a 2xx. The correct receiver design is therefore accept fast, process later: validate, persist the raw event (a database row is fine), return 200, and do the real work — API calls, emails, inventory updates — asynchronously. A receiver that does heavy processing inline will time out under load, get retried, and melt.
2. Duplicates are a feature, not a bug
Because providers retry on any ambiguity (they sent it, your 200 got lost in transit), at-least-once delivery is the standard contract — meaning you will receive some events twice. Your handler must be idempotent: track processed event IDs and silently skip repeats. The classic failure is the customer who got three "order confirmed" emails; that's not the provider's bug, it's a non-idempotent handler.
3. Order is not guaranteed
Event B can arrive before event A. "Order updated" before "order created" happens in real systems. Handlers that assume sequence corrupt state in rare, maddening ways. The robust pattern: treat events as hints, and fetch current truth from the API when it matters ("something changed on order 123 — let me GET order 123").
4. Your endpoint is a public door
A webhook URL is an unauthenticated-by-default endpoint on the open internet that mutates your data. Anyone who finds it can POST fabricated events. Every serious provider signs its webhooks — an HMAC signature header computed over the payload with a shared secret. Verify the signature on every request and reject failures. On WordPress, where webhook receivers are often REST API routes added by plugins, it's worth confirming your plugin actually verifies signatures rather than trusting whatever arrives.
The decision framework
| Question | Points toward |
|---|---|
| Do you need to react in seconds? | Webhooks |
| Is missing an event catastrophic (payments, orders)? | Webhooks + polling reconciliation (see below) |
| Is the data needed hourly/daily (reports, dashboards)? | Polling |
| Can your endpoint be reliably reachable from the internet? | If no: polling |
| Does the provider even offer webhooks? | If no: polling (the decision makes itself) |
| Are events high-volume but individually unimportant? | Polling (batch reads beat 10,000 tiny POSTs) |
| Do you have capacity to build idempotent, signature-verified, async receivers? | If no, honestly: polling until you do |
A useful rule of thumb: polling for pulls, webhooks for pushes. If your workflow is "periodically synchronize a dataset," poll. If it's "when X happens, immediately do Y," webhook.
The grown-up answer: webhooks for speed, polling for truth
Here's the pattern that production payment and commerce integrations converge on, and the real thesis of this post: use webhooks as the fast path and a slow poll as the safety net.
- Webhooks handle the real-time work — the events arrive in seconds and drive immediate actions.
- A reconciliation job polls on a relaxed schedule — every hour, or nightly — asking "give me everything from the last period" and comparing it against what the webhooks delivered.
- Anything the poll finds that the webhooks missed gets processed (and logged loudly, because a pattern of misses means something's wrong with the endpoint).
This hybrid costs one extra scheduled job and buys you the best property of each pattern: webhook latency with polling's can't-miss completeness. When someone asks "but what if the webhook fails?", the answer is "then we're at most an hour behind instead of wrong forever." For anything involving money, treat reconciliation as non-optional — the question is not whether a webhook will eventually be missed, but when, and what catches it.
WordPress specifics
- Receiving: webhook endpoints on WordPress are typically custom REST API routes (
/wp-json/yourplugin/v1/webhook). Keep handlers thin: verify signature, store payload, return 200, process via a scheduled task. Beware aggressive security plugins and firewalls that block legitimate webhook POSTs — if a provider's dashboard shows deliveries failing with 403s, check your WAF rules before your code. - Polling: WP-Cron only fires when someone visits the site, which makes low-traffic sites poll erratically. For any polling schedule you actually rely on, switch to a real system cron hitting
wp-cron.php(or your host's scheduled-task feature) so "every 5 minutes" means every 5 minutes. - Sending: if your site emits webhooks to others (WooCommerce can, out of the box), remember you've now taken on the provider-side duties — retries, signatures, and a delivery log — or at least, you should verify what your plugin does about them.
Choosing without regret
Start with the freshness requirement, because it's the constraint that doesn't negotiate. Seconds means webhooks; hours means polling; and "seconds, and it involves money" means webhooks plus reconciliation. Then be honest about operational capacity: a webhook receiver done properly needs signature verification, idempotency, async processing, and monitoring — a poller needs a cursor and a cron job. There is no shame in shipping the boring poller this month and graduating to the hybrid when the latency actually pinches.
The integrations that break are rarely the ones built on the "wrong" pattern. They're the ones built on either pattern's brochure version — the poller that fetches "latest 10," the webhook handler that trusts, processes inline, and assumes exactly-once. Pick either pattern like an adult, and it will quietly work for years. That's the highest compliment an integration can earn.
Photo: Tara Winstead on Pexels