Best Way to Receive Updates from APIs Without Webhooks (Without Building Polling Systems)
April 23, 2026
Last updated: September 20 2026
When an API offers no webhooks, the standard answer is incremental polling with an updated_at cursor, and it works, but at scale it becomes a system your team builds and operates: schedulers, cursor storage, pagination, retries, rate-limit handling and reconciliation. The alternative is to move change detection out of your application and receive changes as events. On Unified.to, a virtual webhook does this: it checks the integration's API on an interval you set, as often as every minute on paid plans, and delivers what changed to your webhook URL in the same format as a native webhook, so your application stays event-driven whether or not the underlying API has webhooks.
What are the three ways to receive updates from an API?
There are three: your application polls the API, the API sends webhooks, or an integration layer detects changes and delivers them as events. Most APIs support the first; many support the second for some objects; the third depends on the platform you build on. For a fuller comparison of the first two, see Polling vs Webhooks: When to Use One Over the Other.
| Approach | How it works | Tradeoffs | What you maintain |
|---|---|---|---|
| Polling | Your app repeatedly calls the API to check for updates | Universal; latency equals the interval; most requests find nothing | Scheduler, cursors, pagination, retries, backoff, reconciliation |
| Native webhooks | The API sends an event when a record changes | Fast; not available on most APIs, or only for some objects | Endpoint, signature verification, idempotency |
| Event delivery layer (virtual webhooks) | The integration layer checks the API, detects changes, delivers events | Same interface as native; latency equals the configured interval | Endpoint, signature verification, idempotency |
What is incremental polling, and why is it the default?
Incremental polling is a scheduled read that asks the API for records changed since a stored cursor, usually an updated_at timestamp or a change token, followed by an idempotent upsert. It's the default because it needs nothing from the API beyond a list endpoint with a since-filter, which nearly every API has.
To make it reliable across many customers, a team ends up building schedulers across tenants, cursor storage per resource, pagination handling, retry and backoff logic, rate-limit coordination, and reconciliation jobs for anything missed. At scale, most of those scheduled reads return nothing while still consuming the API's quota and your infrastructure.
What are the other pull-based options, and where do they stop?
Four variations reduce the cost of polling without removing it: conditional requests, delta queries, long polling and snapshot diffing. Each still requires your application to ask, and each depends on the API supporting it.
Conditional requests (ETag, If-Modified-Since) let the server answer 304 Not Modified with no body when nothing changed, as defined in RFC 7232. They cut payload size, but the request still counts, they don't tell you which records changed, and support is inconsistent across APIs.
Delta queries return only what changed since a token you pass back. This is the cleanest pull model where it exists. It's provider-specific in shape, tokens expire and reset in ways you have to handle, and few SaaS APIs offer one.
Long polling holds a request open until something changes. It needs the API to support it, adds connection management, and doesn't generalize across integrations.
Snapshot diffing fetches the whole dataset, compares it with the previous state, and infers changes. It's the last resort where no cursor exists, and it's expensive and slow on large datasets. Some integration platforms run this model on your behalf: as of September 2026, Apideck's help centre describes its virtual webhooks as retrieving all items per resource on each cycle, "typically every 24 hours," with a one to two minute pause between pages.
Why does polling turn into infrastructure?
Polling turns into infrastructure because the guidance to "use incremental polling" stops where the work starts: scheduling across tenants and integrations, tracking cursors, handling retries and failures, managing rate limits, and reconciling missed updates are each a component, and together they're a system. Teams that make polling reliable end up with API reads, change detection, internal event generation and downstream consumers. In other words, they convert pull-based APIs into an event-driven system, which is the same pattern integration platforms and data pipelines use.
What does centralizing change detection change?
Centralizing change detection moves the scheduled reads, the cursors and the retry logic into one place that reads from source APIs, detects changes using timestamps or cursors, and delivers those changes as events, so your application only receives. The checks against the API still happen; they just don't live in your codebase, and they're written once rather than once per integration.
How does Unified.to handle APIs without webhooks?
Unified.to provides one webhook interface across integrations, using the integration's native webhooks where they exist and virtual webhooks where they don't, so your application receives the same signed POST in either case. As of September 2026:
- When native webhooks exist, Unified.to registers the subscription with the integration and forwards events as they arrive. Where the integration's event carries only an identifier, Unified.to fetches the full record before delivering it.
- When they don't, a virtual webhook checks the integration on your interval, as often as every 1 minute on paid plans and every 60 minutes on the free plan, detects changes using timestamps and cursors, and delivers them. Nothing is stored between checks; a failed delivery is retried by re-reading the same page from the integration.
- The payload is the same for both: an array of records in the API's format, a
typemarker, your connection'sexternal_xref, and an HMAC-SHA256 signature.created,updatedanddeletedevents, withdeletedon native webhooks and on virtual where the integration supports it. - Historical data comes through the same subscription. Create the webhook with
include_all=trueand existing records are delivered in pages, taggedINITIAL-PARTIALthenINITIAL-COMPLETE, before ongoing events begin. - Rate limits, retries and backoff are handled for you. Checks that hit the integration's rate limit back off and honour its retry-after guidance. Failed deliveries to your endpoint are retried three times immediately; virtual webhooks then keep retrying with backoff for up to roughly two weeks, while native webhooks are marked unhealthy after the immediate retries.
- Health is yours to watch. Every webhook has an
is_healthyfield; an unhealthy one stops and doesn't resume until you fix the cause and recreate it. Subscribe toWEBHOOK_UNHEALTHYon the notifications webhook so you find out; How to troubleshoot unhealthy webhooks covers diagnosis and recovery. - Billing follows changes, not checks. Checks that find nothing aren't billed; you're billed per page of changes successfully delivered.
Which type an integration supports, per object, is on its Feature Support tab in the dashboard. Setup is covered in How to create and configure webhooks, and the difference between this model and platforms that store your data and notify you after a sync is in Which Unified API Platforms Support Virtual Webhooks vs Sync-Based Notifications.
Why does where the polling lives matter?
It matters because the question isn't "how do I poll better" but "why is polling infrastructure inside my application at all." Polling is an implementation detail of reading an API that can't push. The decision is where that system lives: in your codebase, per integration, maintained by your team, or in the integration layer, written once, with your application receiving events.
What is the takeaway?
For APIs without webhooks, the default is polling, and polling becomes a system at scale: change detection, event routing, retry and rate-limit handling. The more scalable approach is to centralize that system and receive updates as events. Unified.to does this by forwarding native webhooks where they exist and detecting changes and delivering events where they don't, so your application stays event-driven without building or maintaining polling infrastructure.
Frequently asked questions
Do virtual webhooks eliminate requests to the integration's API? No. The checks still call the integration's API and count against its rate limits; Unified.to backs off automatically when it's rate-limited. What's eliminated is the polling code in your application and any charge for checks that find nothing.
What if the API has no updated_at field?
Change detection depends on what the integration exposes. Where an object has no usable timestamp or cursor, virtual webhooks may not be available for it. Check the object on the integration's Feature Support tab before building on it.
Can I get existing records as well as changes?
Yes. Create the webhook with include_all=true and Unified.to delivers the connection's existing records in pages before switching to ongoing events. This works for native and virtual webhooks alike.
How is this different from a platform that syncs my customers' data and notifies me? A sync-based platform stores a copy of the data on its schedule and sends a notification when the sync completes, which you follow with an API call to fetch what changed. A virtual webhook reads from the source, holds nothing between checks, and delivers the changed records in the event itself.