The usual framing is that polling is the naive approach and webhooks are what you graduate to. It's a tidy story and it leads teams to the wrong architecture surprisingly often.
The useful question isn't which is more efficient. Webhooks are, and it isn't close. The useful question is what each one does when it breaks, because that's the property you'll be living with at 3am, and the two are almost exact opposites.
TL;DR
- Polling fails loudly and recoverably: you notice, and the next poll catches up
- Webhooks fail silently and permanently: nothing errors, the event is simply gone
- Webhooks force you to build infrastructure polling gives you free: retries, dedupe, ordering, and signature verification
- Most production integrations end up hybrid: webhooks for latency, a reconciliation poll for correctness
- If you can only build one, and correctness beats latency, poll
The comparison that's usually presented
| Polling | Webhooks | |
|---|---|---|
| Latency | Your interval, average half of it | Near-real-time |
| Wasted requests | Most of them | None |
| Who bears cost | You | The sender |
| Public endpoint required | No | Yes |
| Ordering | You control it | Not guaranteed |
| Setup | An HTTP call and a loop | Endpoint, TLS, signature verification, dedupe, retries |
Read that table and webhooks win on the rows people usually optimize for. Which is why so many teams switch, ship it, and then quietly lose events for two months.
The comparison that actually decides it
Ask instead: when this breaks, how do I find out?
Polling degrades in ways that announce themselves. Your server is down, so the loop doesn't run. When it comes back, the next poll asks "what changed since my last cursor" and gets everything. Recovery is automatic and requires no code you didn't already write. If the API is down, your poll returns 500 and you see it in your own error rate immediately. The pull model means you always know your own last known good position, and that single property is worth an enormous amount.
Webhooks fail in ways that are invisible from your side. Your endpoint 500s for six hours during a bad deploy. The provider retries, gives up, and moves on. Your logs show nothing unusual, because you can't log a request you never received. There's no cursor, no gap detector, no "I'm 400 events behind" number anywhere in your system. Absence of data looks identical to absence of activity, and on a quiet Sunday you cannot tell them apart.
That asymmetry should decide it. Polling's worst case is stale data you'll catch up on. Webhooks' worst case is missing data you'll never know about.
The second-order costs follow from it. With webhooks you now own:
- Retry logic, because the provider's window is finite and generous providers still give up after three days
- Idempotency, because at-least-once delivery means duplicates are guaranteed
- Ordering guards, because retries let event #2 overtake event #1
- Signature verification, because your endpoint is public and anyone can POST to it
- A public HTTPS endpoint that's always up, which is a different availability requirement than "our app is usually up"
None of these are hard individually. Together they are the reason "just use webhooks" turns into a two-week project that isn't finished when you think it is.
When polling is genuinely the right answer
Don't let the efficiency argument talk you out of these.
The obvious one is latency you don't need. A nightly accounting sync, an inventory reconciliation, a CRM enrichment job: if nobody notices a 5-minute delay, you're paying real complexity for a property with no value to anyone.
Then there's having nowhere to receive. Desktop apps, internal tools behind a VPN, a job running on somebody's laptop. Tunnelling a webhook into localhost is a development technique rather than a production architecture, and we wrote about where that boundary sits in Why ngrok Isn't Enough.
The one teams most often get wrong is when correctness beats latency and the deadline is a week away. A cursor-based poll you can reason about beats a webhook pipeline you half-finished.
Low volume tips the same way. A poll every 5 minutes is 288 requests a day, which no rate limit notices, and the operational simplicity comes free. So does a provider whose own webhooks are flaky: if their delivery is unreliable and their event log is thin, polling their API is more trustworthy than trusting their sender.
When webhooks earn their complexity
Webhooks pay for themselves when a human is waiting. Payment confirmation, deploy status, a chat message. Sub-second matters, and polling fast enough to fake it means hammering an API.
They also win when events are rare and unpredictable. Polling for something that happens twice a day means roughly 99.9% of your requests return nothing, and that is the case webhooks were designed for. At the other extreme, volume can make polling infeasible outright: ten thousand shops each needing 30-second freshness is 28.8 million requests a day, and you'll hit rate limits long before you get there.
The last case is long-running jobs. Video transcode, model inference, batch export. The provider knows when it's done and you'd be guessing, which is why AI and media APIs push webhooks hard. See Webhooks for AI Agents.
The hybrid, which is what you'll build anyway
Nearly every mature integration ends up here, and arriving deliberately is cheaper than arriving after an incident.
Webhooks carry the latency. A periodic reconciliation poll carries the correctness.
// Fast path: webhook, near-real-time
export async function POST(req: Request) {
const event = await verify(req);
await upsertEvent(event); // idempotent by provider event id
return new Response(null, { status: 200 });
}
// Safety net: every 15 minutes, ask what we should have
async function reconcile() {
const since = await getLastReconciledAt();
for await (const obj of provider.list({ updated_since: since })) {
await upsertEvent(obj); // same idempotent path, no-op if webhook won
}
await setLastReconciledAt(now);
}
The reconcile job costs you a scheduled function and reuses the code you already wrote. In exchange it converts webhooks' worst property, silent permanent loss, into "up to 15 minutes stale." That is a completely different risk.
The requirement that makes it work: both paths go through the same idempotent writer. If the webhook already handled it, reconcile is a no-op. If it didn't, reconcile fixes it. This only holds if you deduplicate on the provider's event ID rather than on your own arrival time. The patterns are in You Received the Same Webhook Twice.
Instrument the overlap. Count how often reconcile finds something the webhook missed. That number is your webhook pipeline's real loss rate, and it is the only honest measurement of it you will ever get. If it's zero for a month, your webhooks are healthy. If it spikes, you have an outage you'd otherwise never have detected.
The middle ground people forget
Polling doesn't have to be dumb.
Cursor-based polling, meaning ?since=<cursor> rather than "fetch everything and diff", is dramatically cheaper and gives you exactly-once semantics for free, because the cursor only advances on successful processing.
Conditional requests, If-None-Match with ETags or If-Modified-Since, return a 304 with no body. Many providers don't count those against your rate limit. You can poll aggressively for very little.
Long polling gets you webhook-like latency with pull-model recovery. Rarer, but where it's offered it's often the best of both.
If you've dismissed polling because of the cost of fetching everything on a fixed interval, you may have been comparing against the worst version of it.
Where AnyHook fits
The reason webhooks fail silently is that nobody keeps a record of what should have arrived. AnyHook is that record.
Providers POST to in.anyhook.net/you/app. Every event is persisted before delivery is attempted, so:
- The gap becomes visible. Delivery failures are counted, surfaced, and alerted on at 1, 5, and 20 consecutive failures. "Silent" stops being accurate
- Recovery is a replay, not a reconciliation job. Select the window your server was down and re-deliver from the log. Replays don't consume quota
- The infrastructure tax is paid once, by us. Retries with exponential backoff, a durable log, and a single
AnyHook-Signatureheader instead of a verification scheme per provider
This doesn't remove the case for a reconciliation poll. If a provider never sent the event, no relay can conjure it. But it collapses the failure mode from "silent and permanent" to "logged, alerted, and replayable," which is most of what the hybrid was buying you.
Takeaway
Pick on failure mode. Polling is late but self-healing; webhooks are fast but lose things quietly. If latency doesn't affect a human or a rate limit, polling is not the beginner option. It is the one with the better worst case. And if you do run webhooks in production, add the reconciliation poll before you need it, because the metric it produces is the only way you'll ever know how many events you're actually losing.