FlowKit

Automating an app with no webhook in n8n: smart polling

Published 7 August 2026 · 6 min read

An internal CRM with no event-driven API, a line-of-business ERP hosted by a vendor that only offers plain REST, a SaaS platform that gates webhooks behind its Enterprise tier: most n8n automations don't start from an event pushed by the application — they have to go fetch the information themselves. This is more common than it might seem — a study by Paul Murley, Zane Ma, Joshua Mason, Michael D. Bailey, and Amin Kharraz, “WebSocket Adoption and the Landscape of the Real-Time Web”, presented at The Web Conference 2021, shows that across the web as a whole, periodic polling remains far more prevalent than event-driven mechanisms like WebSocket, even for use cases that would benefit from a real-time stream. Polling isn't some shameful fallback, then — it's a legitimate integration pattern in its own right, provided you build it properly in n8n instead of wiring a Schedule Trigger to an HTTP Request and hoping it holds up.

Why so many applications don't expose a webhook

Before building anything, it's worth genuinely verifying the absence of a webhook rather than assuming it:

  • Line-of-business software and ERPs (accounting, inventory, internal CRM): designed before the era of event-driven APIs, they often expose plain CRUD REST or SOAP, with no change notifications.
  • Paid-tier feature: many SaaS products (support, billing, e-commerce) reserve webhooks for their higher-tier plans, while the read API stays available on every subscription level.
  • Restrictive network policy: some companies block inbound connections to their internal systems for security reasons, making a webhook physically impossible to receive — see our guide to securing n8n webhooks for why that choice makes sense.
  • Poorly documented or unofficial third-party API: the webhook exists on the vendor's side but isn't publicly exposed, or only through a partnership that's out of reach.

In all four cases, polling isn't a temporary workaround — it's the permanent architecture, and it deserves to be designed as one.

The smart-polling architecture in n8n

Naive polling fetches everything on every run and filters afterward — which works fine on 50 records and falls over on 50,000. A proper architecture splits the work into three distinct steps.

1. The Schedule Trigger asks, the API filters

The Schedule Trigger node fires the execution at a regular interval, but the real work happens in the HTTP Request node that follows: instead of fetching everything, use the server-side filtering parameters that almost any decent REST API offers — updated_since, created_after, a sort=desc parameter combined with pagination that stops as soon as it reaches the first already-known record (see our guide to pagination with HTTP Request). Letting the API do the filtering, rather than n8n after the fact, cuts down the volume of data transferred and the number of items to process downstream — exactly the kind of gain documented in a reference study on web communication protocols: Pimentel and Nickerson, “Communicating and Displaying Real-Time Data with WebSocket”, published in 2012 in IEEE Internet Computing, precisely quantifies the network overhead of periodic polling compared to event-driven communication — a useful reminder not to make that overhead worse with poorly filtered polling.

2. A cursor remembers what's already been seen

The workflow needs to know, from one run to the next, where it left off. That's exactly what $getWorkflowStaticData() is for — we cover its full syntax, its pitfalls (notably around test executions), and its limits in queue mode in our dedicated n8n static data guide. In short, for polling: store the timestamp or the most recent successfully processed ID, and use it as a parameter for the next request. On a queue-mode instance with several workers (see our Redis queue mode guide), prefer a dedicated Supabase or Postgres table: static data is attached to the main instance, not shared across workers.

3. A defensive filter catches leftover duplicates

Even with a reliable cursor, keep a safety net: a Filter node that checks each incoming ID against a short list of the most recently processed identifiers, before letting a record continue downstream. This logic is covered in detail in our article on idempotency and avoiding duplicates: the same principles apply whether it's a replayed webhook or a polling run that slightly overlaps with the previous one.

Choosing the interval: a trade-off, not a default number

Plenty of workflows default to "every 5 minutes" without thinking it through. Three factors should actually drive that choice:

  • The freshness you genuinely need: a daily digest tolerates hourly polling; a critical stock-out alert justifies an interval of a few minutes. Polling more often than the business need requires only burns resources, with no perceived benefit.
  • The target API's rate limit: calculate the number of calls allowed per day, subtract a 20-30% margin for retries after errors (see our guide to 429 errors and pacing API calls), and derive your minimum viable interval from that.
  • The real volume of new records: if the application generates roughly one new record per hour on average, polling every five minutes just multiplies empty calls by twelve, for a latency gain the end user will rarely notice.

A concrete case: monitoring orders in an ERP with no webhook

A representative example of the full pattern: an internal order-management ERP, with no event-driven API, needs to feed an automation that follows up on incomplete records — the same business need covered in our guide to following up on incomplete records, but with no webhook available on the source side.

  1. Schedule Trigger every 15 minutes, business hours only (multiple Trigger Rules, as described in our Schedule Trigger guide).
  2. HTTP Request to the ERP's API with a modified_since=<cursor> parameter, sorted by ascending modification date.
  3. Defensive Filter against the list of most recently processed IDs, stored in static data.
  4. IF node to distinguish a brand-new order from one that's been incomplete for more than 48 hours.
  5. Set node to update the cursor, written at the end of the workflow only if the run completed without error — otherwise the next pass re-checks the same records, which is the desirable fallback behavior.
  6. Slack notification or AI digest, following the same principle as the workflows in the Inbox AI Pack and the Compliance & Audit Pack, which already ship this kind of cursor-and-deduplication logic ready to use.

When to switch to a real webhook

Polling is always a compromise, never a goal. As soon as a target application opens up a webhook — an upgrade to a higher plan, an API update, a partner access finally granted — the switch deserves to be planned rather than pushed off indefinitely: near-zero latency, no wasted calls, and load on the third-party API cut down to the strict minimum. Our complete n8n webhooks guide covers the setup, and the transition is usually quick: the Schedule Trigger and HTTP Request disappear, while the rest of the workflow (defensive filter, processing, notification) stays the same — the deduplication logic described above protects just as well against duplicate webhook deliveries.

Bottom line

Polling isn't a temporary hack — it's a legitimate integration architecture whenever a webhook isn't available, provided you filter on the API side, keep a reliable cursor, hold on to a deduplication safety net, and calibrate the interval to real need rather than habit. The FlowKit packs already apply these principles in their monitoring and follow-up workflows — a proven base to build on rather than reinventing cursor logic for every new integration.

FAQ

Frequently asked questions

What polling interval should I choose to avoid hitting an API's rate limit?

Start from the real expected volume of new records rather than a round number: if a new record appears roughly once an hour, polling every five minutes adds almost nothing and multiplies your calls by twelve. Then check the target API's rate-limit documentation and compute your theoretical daily call ceiling; stay 20-30% below it to absorb spikes and retries after network errors.

Is n8n's Static Data node enough to store the polling cursor?

Yes, for a single workflow on a standard-mode instance: static data survives between executions and works well for a timestamp or the last processed ID. It becomes insufficient in queue mode with several workers, or as soon as multiple workflows need to share the same cursor — switch to a dedicated Supabase or Postgres table at that point.

How do I avoid processing the same record twice with polling?

Never filter only on the n8n side after fetching everything: ask the API to return only what's newer than your last cursor (an updated_since, since_id, or equivalent parameter) whenever it supports it, then add a defensive filter on the n8n side that checks each ID against a list of the most recently processed identifiers before passing records downstream.

Does polling really consume more resources than a webhook?

Yes, structurally: a webhook only generates traffic when an event actually happens, while polling queries the API at a fixed interval even when there's nothing new. The gap becomes significant at higher volume, which is why you should switch to a webhook as soon as the target app offers one, and reserve polling for cases where it's the only option available.

Bundle FlowKit Complet

€269