Ask a systems engineer whether to use webhooks or polling and you'll get a reflexive answer, because the industry settled this a decade ago: stop polling. Polling is wasteful — you hammer a status endpoint that says "not yet" a hundred times before it says "done." Webhooks are elegant — the server tells you the instant something happens, and you spend zero requests waiting. Every API-design guide of the 2010s pointed the same direction, away from the poll and toward the push.
Then agents arrived and quietly broke the assumption that made webhooks better. It's worth being precise about what broke, because the fix isn't nostalgia for polling — it's understanding why the newest async surfaces, the ones designed for agents from scratch, ship polling as the primary path.
The assumption a webhook actually makes#
A webhook is a callback. For the server to call you back, you have to be callable — a durable process listening on a publicly-reachable HTTPS URL that stays up between the moment you submit the job and the moment it finishes. In a world of long-lived backend services, that assumption is free. Your service already has a public ingress; adding a POST /webhooks/task-done route costs nothing.
An agent is not that world. An agent step is frequently an ephemeral process — a function invocation, a sandboxed container, a job that exists only for the duration of one reasoning turn and then evaporates. It often runs inside an isolation boundary with no inbound network route at all, precisely because you don't want arbitrary tool-executing code to be reachable from the internet. The waiting party has no address. There is nowhere to push to.
A webhook assumes the client is a server. The whole point of a sandboxed agent is that it isn't one.
That single mismatch flips the calculus. Polling's supposed weakness — that the client drives the interaction — is exactly what an agent needs, because all an agent can reliably do is make an outbound request. A GET /tasks/{id} works from inside a sandbox, from a serverless function, from a process that will be gone in ninety seconds. The elegant push doesn't.
The evidence is in the new specs#
You can watch the field re-learn this in the interfaces being shipped right now.
The Model Context Protocol's 2026-07-28 release candidate rebuilt the protocol to be stateless, and with it introduced a Tasks extension for long-running work. A tools/call can return a task handle, and the client drives it with tasks/get, tasks/update, and tasks/cancel. Notice what's not there: a push. Because a stateless server can't hold a per-client channel open, tasks/get polling is the mechanism — and the spec went as far as removing tasks/list, since listing tasks only made sense under the old session-bound model. For long-running MCP work, the client pulls. (If you're implementing this, our walkthrough of the MCP Tasks lifecycle and why the spec went stateless has the details.)
The batch APIs tell the same story. Anthropic's Message Batches process asynchronously with a 50% discount — and there is no completion webhook. You poll, roughly every 30–60 seconds, until the batch reports done. OpenAI's Batch API is the same shape: you submit, then query a status that walks through validating → in_progress → finalizing → completed. The two largest model providers built their async surface poll-first, on purpose.
The rule that actually holds: layer them#
None of this means webhooks are dead. It means the two mechanisms occupy different jobs, and the mistake is treating them as alternatives instead of layers.
Poll for correctness. Polling is the only mechanism whose failure mode is safe. Miss a poll and you simply poll again; the authoritative state is always sitting at the status endpoint waiting to be read. A webhook's failure mode is the dangerous one — it is delivered at-most-once in practice. It gets dropped by a firewall, fails silently while your endpoint is redeploying, or arrives out of order after a retry. When a webhook is lost, nothing happens, and "nothing happened" is indistinguishable from "still running." A design that trusts the webhook as its source of truth will, eventually and invisibly, wait forever.
Webhook to save the polls. Once polling is your backstop, a webhook becomes a pure optimization: a hint that says "the state probably changed — poll now instead of on your next scheduled tick." You get near-instant latency when the push lands, and full correctness when it doesn't, because the poll loop underneath never trusted it. This is the same discipline the mature guidance already reaches for: Tyk and Zuplo both land on polling as the reliable spine, webhooks as the accelerator.
The plumbing underneath is the decade-old Asynchronous Request-Reply pattern: the submit returns 202 Accepted with a Location header pointing at the status endpoint; the client polls that URL, honoring Retry-After, until it returns a terminal state. Two details matter more for agents than for anyone else. Attach an Idempotency-Key to the submit so a retried request — and agents retry constantly — returns the same task instead of quietly starting a second expensive run; if you haven't wired that up, making tool calls idempotent is the prerequisite. And cap the total attempts with backoff and jitter, so a wedged task fails closed rather than polling until your bill notices, and so a fleet of agents doesn't synchronize into a thundering herd against one endpoint.
The old advice wasn't wrong for its world. It's just that "use webhooks" encoded an assumption — the client is a reachable server — that the agent runtime deletes. Design for the client you actually have: an outbound-only process that can pull, but can't be pushed to. Poll for truth, push for speed, and never let the optimization become the guarantee.



