n4nAI

Polling vs webhooks for async agent completion

A practical engineering comparison of polling vs webhooks agents for async completion: latency, cost, ergonomics, limits, and which to use per use case.

n4n Team4 min read891 words

Audio narration

Coming soon — every post will get a voice note here.

Polling vs webhooks agents is a foundational architectural decision when you run long-lived LLM workflows that outlive a single HTTP request. Get it wrong and you either burn compute on empty requests or miss completions because your endpoint silently dropped. This piece compares both patterns across the dimensions that actually matter in production.

Head-to-head summary

Dimension Polling Webhooks
Capabilities Client pulls status/logs on own schedule; works behind NAT/firewalls Server pushes completion + events; near-real-time delivery
Cost model Steady drain from idle GETs (compute + network) Outbound egress + always-on receiver infra
Latency Bounded by poll interval; tail = interval Milliseconds after completion; retry storms add tail
Throughput Degrades as N agents × poll rate Scales with completion rate, not active watchers
Ergonomics Trivial: no public URL, no signature check Requires reachable HTTPS, secret verification, idempotency
Ecosystem Universal HTTP; every orchestrator supports it Provider-dependent event schemas; fragmented
Limits Status endpoint rate limits; max poll count Retry caps (3–5); delivery timeout ~10s; payload caps

The polling vs webhooks agents trade-off shows up in every row above. The sections below unpack each.

Capabilities

Polling gives you a synchronous-looking control flow in an async context. You call GET /runs/{id} and decide what to do with the JSON: read status, pull token_count, fetch partial artifacts. This works behind corporate proxies, inside serverless ephemeral IPs, or in a nightly batch job with no inbound route. That universality is why every async job API since the early 2000s ships a status endpoint.

Webhooks invert control. The agent runtime POSTs to you when the run finishes, fails, or hits a checkpoint. You receive a richer payload pushed directly to your handler, and you can react without a timer. The cost: your service must be addressable from the internet, and you must handle duplicate deliveries (most providers retry on 5xx or timeout).

# Polling with incremental log fetch
import time, requests

def follow_run(run_id, token, interval=2, timeout=900):
    seen_logs = 0
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"https://api.example.com/runs/{run_id}",
                         headers={"Authorization": f"Bearer {token}"})
        r.raise_for_status()
        data = r.json()
        if len(data.get("logs", [])) > seen_logs:
            for line in data["logs"][seen_logs:]:
                print("agent:", line)
            seen_logs = len(data["logs"])
        if data["status"] in ("succeeded", "failed"):
            return data
        time.sleep(interval)
    raise TimeoutError("run did not finish in time")
// Webhook receiver (Express) with at-least-once handling
app.post("/agent-hooks", (req, res) => {
  const sig = req.headers["x-signature"];
  if (!verifyHmac(sig, req.rawBody, process.env.HOOK_SECRET)) {
    return res.status(401).end();
  }
  const evt = req.body; // {run_id, status, ts, attempt}
  if (alreadyProcessed(evt.run_id, evt.attempt)) {
    return res.status(200).end(); // dedupe
  }
  void persistEvent(evt).then(() => ack(evt)); // ack after durable write
  res.status(202).end();
});

Price/cost model

Polling cost is stealthy. Each empty GET costs a lambda invocation, a database read, and sometimes a provider API call if the gateway doesn’t cache status. Assume 500 concurrent agents averaging two-minute runs, polled every 3 seconds: that is ~20,000 requests per minute of pure noise. At typical cloud API gateway pricing of a few dollars per million requests, you spend tens of dollars daily just asking “done yet?” Webhooks shift cost to the sender’s egress and your always-on receiver. If you run one container with a URL, marginal cost per completion is near zero until you need autoscaling.

A gateway like n4n.ai can shield inference from provider degradation via automatic fallback across 240+ models, but the status-check traffic you generate via polling is billed by your own infrastructure, not the model layer.

Latency/throughput

Polling latency equals your interval plus processing jitter. Set interval to 1s for snappy UX and you amplify cost linearly. Webhooks deliver within milliseconds of completion, but only if your endpoint acknowledges fast; providers often cap total delivery time (e.g., 10s) and then retry later, adding tail latency on failure.

Throughput under polling is agents × poll_rate. Webhooks scale with event rate. With 10,000 agents but only 50 finishing per minute, webhooks generate 50 calls; polling generates 10,000×rate. At scale, the math is brutal.

Ergonomics

Polling wins for local dev. No tunnels, no shared secrets for signature verification, no callback URL registration. A cron job or a while loop suffices. Webhooks demand operational maturity: HTTPS certs, replay protection, idempotency keys, and a dead-letter queue for failed deliveries.

# Local dev with webhooks often needs a tunnel
ngrok http 8080 # exposes ./agent-hooks to the internet
# then register https://<id>.ngrok.io/agent-hooks as callback_url

Testing webhooks means shipping signed fixtures or running a contract test against your verifier. Polling tests are just unit tests on a status object.

Ecosystem

Every LLM orchestration framework supports polling because it is just HTTP. OpenAI’s batch API is poll-only; LangGraph and similar expose both. Webhook support is spottier: some platforms emit OpenAI-style callback_url, others use proprietary event buses. If you standardize on webhooks, you couple to each provider’s event schema. The polling vs webhooks agents debate is partly a portability question—polling travels; webhooks lock you into whoever sends the POST.

Limits

Polling hits rate limits on the status endpoint; providers may throttle or charge after N polls per run. Webhooks fail silently if your endpoint returns 200 but crashes processing later—providers consider it delivered. Retry caps (typically 3–5 attempts) mean you must ack only after durable persist. Payload size limits (often 1–4 MB) can truncate large agent traces unless you fetch them via subsequent polling.

Which to choose

Use polling when:

  • You run agents inside a secured VPC with no inbound route.
  • Agent counts are low (<100) and completion SLAs are seconds, not milliseconds.
  • You prototype and want zero external dependencies.
  • Provider webhook schemas are unstable, absent, or you need to read intermediate state.

Use webhooks when:

  • You orchestrate thousands of concurrent long-running agents and poll cost dominates.
  • Your product needs immediate user notification (e.g., “report ready”).
  • You already operate a resilient public endpoint with idempotent handlers and DLQ.
  • The platform guarantees at-least-once delivery with signature headers.

Hybrid pattern: Start with polling for dev, then switch to webhooks in production via a config flag. Many teams poll with a long interval (30s) as a backstop against missed webhooks—cheap insurance against a dropped POST.

The polling vs webhooks agents decision is not ideological. It is a trade of infrastructure burden against request waste. Pick the side that matches your scale and network topology, and keep the other as fallback.

Tagspollingwebhooksasync-agentsagent-orchestration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All long-running & asynchronous agent workflows posts →