Most integration bugs are not bugs. They’re the consequence of a decision made in week one that nobody wrote down. You chose polling because it was faster to implement. Six months later, your server is hammering a third-party API every thirty seconds, you’re eating rate limit errors at 2am, and your users are seeing stale data with no obvious explanation. The fix is not a patch. It’s an architectural rewrite you didn’t budget for.
This is one of the most quietly destructive decisions in SaaS product builds, and it almost never comes up in scoping conversations. It should. Here’s how we think about it.
What You’re Actually Choosing Between
Polling means your system goes and asks: “Has anything changed?” on a schedule. Every sixty seconds, every five minutes, whatever you set. It’s easy to implement, easy to reason about, and works fine when you have ten users and your third-party API doesn’t care.
Webhooks mean the third-party system tells you when something changes, by sending an HTTP POST to an endpoint you control. Your system is reactive instead of proactive. The data arrives the moment something happens – not the next time you happen to check.
That difference sounds academic. It stops being academic when you have 500 tenants each with a connected Xero account, and your polling loop is making 500 API calls every minute. Xero has rate limits. You will hit them. Your users will notice.
When Polling Is Actually Fine
Let’s be honest: polling is not always wrong. There are situations where it’s the correct call, and treating webhooks as universally superior will lead you astray.
- The third party doesn’t support webhooks. Plenty of older or niche Australian government APIs, industry data feeds, and legacy SaaS tools simply don’t offer them. You have no choice.
- You need data from a system you don’t control the credentials for. Some integrations require scraping or read-only API keys where push events aren’t an option.
- The data is time-series or batch by nature. If you’re pulling yesterday’s transaction summary at 6am, polling on a cron schedule is exactly right. Real-time delivery adds nothing.
- Your user count is small and growth is far off. An MVP with 20 users and a five-minute poll interval? Fine. Ship it, validate the product, revisit when it matters.
The mistake is not choosing polling. The mistake is choosing polling by default and never revisiting it. Build the poll, but flag it clearly in your codebase as a pattern to migrate when volume warrants it.
The Real Cost of Getting This Wrong at Scale
We worked with a PropTech platform that had built a Salesforce integration on polling – every two minutes per active tenancy. When they hit a few hundred active agencies, the API call volume became unsustainable. Salesforce’s API limits are per org, and they were burning through daily limits before lunchtime. The fix required rebuilding the integration layer, re-architecting how they stored state, and re-testing across every customer’s configuration. That was a six-figure refactor that could have been avoided with a different initial decision.
The subtler cost is support load. Stale data is invisible to your engineering team and infuriating to users. They lodge tickets saying “the sync is broken” when technically it’s working – it just hasn’t polled yet. You burn time diagnosing problems that are architectural, not logical.
Webhook failures are a different kind of problem, but they’re more transparent. If a webhook delivery fails, the third-party retries. You log the failed payload. You have something to work with. A polling gap just silently doesn’t happen.
How to Build a Webhook Architecture That Doesn’t Fall Over
Receiving a webhook correctly is harder than it looks. Most tutorials show you the happy path. Production is not the happy path.
Here’s what a production-grade webhook ingestion layer looks like when we build it:
- Endpoint receives and immediately acknowledges. Your webhook endpoint should do one thing: write the raw payload to a queue and return a 200. If you try to process the event synchronously inside the HTTP handler, you’ll time out on slow operations and the sending system will retry, which creates duplicates.
- Queue-based async processing. We typically use SQS, Cloud Tasks, or a managed job queue like Inngest depending on the stack. The raw event sits there until a worker picks it up. Retries, dead-letter queues, and backoff are handled at the queue layer, not in your application code.
- Idempotency from the start. Every webhook event should have a unique ID. Your worker should check whether it’s already processed that ID before doing anything. Third-party systems retry deliveries – sometimes aggressively. You will receive duplicates. If your code isn’t idempotent, you’ll double-process orders, double-send emails, and double-charge customers.
- Signature verification. Every serious webhook provider (Stripe, Xero, GitHub, etc.) signs their payloads with a secret. Verify that signature before you do anything with the data. An unverified webhook endpoint is a security problem waiting to happen.
- Structured logging of every payload. Not just errors – every inbound event, with timestamp, event type, and outcome. When something goes wrong three weeks from now, you will want this. It costs almost nothing to store.
The infrastructure cost for this pattern is minimal – we’re talking under $100 AUD/month for most early-stage products. The engineering cost to build it properly is maybe an extra two to three days over a naive polling implementation. That investment pays back quickly.
The Hybrid Pattern Most Products Actually End Up Using
Here’s the thing nobody tells you at the start: most mature SaaS products use both. Webhooks for real-time events. Polling as a reconciliation backstop.
Webhooks are not perfectly reliable. Delivery can fail. Your endpoint can be briefly unavailable during a deploy. A third-party system can have an outage and not replay missed events. If your data correctness depends entirely on webhook delivery, you have a single point of failure.
The pattern we reach for on anything customer-facing and financially consequential is: receive events via webhook for real-time updates, but run a reconciliation poll once per hour or once per day to catch anything that slipped through. The poll isn’t doing the heavy lifting – it’s a safety net. It also means you can run it infrequently enough that API rate limits are a non-issue.
A payments platform we built last year uses Stripe webhooks for immediate order state updates and a nightly reconciliation job that compares Stripe’s transaction list against the local database. The webhook handles 99.8% of updates in real time. The nightly job catches the edge cases. Users see accurate data. Finance doesn’t have to chase discrepancies.
How to Make This Decision in Your Own Build
Ask yourself these questions before you touch a line of integration code:
- Does the third-party API support webhooks? (Check their docs, not their marketing page.)
- What’s the acceptable latency for my users? Real-time? Minutes? Hours?
- How many tenants or connected accounts will I have at 100, 500, 1000 users?
- What’s the rate limit on the API I’m polling, and have I done the maths?
- What happens to my user experience if an event is missed or delayed?
If you work through those questions and polling still makes sense, do it – but document why, and put a note in the backlog to revisit it when your user count hits a threshold you define now, not later.
If you’re building something where real-time data correctness matters, or where you’re expecting meaningful scale within twelve months, the webhook-first architecture with a polling backstop is almost always the right call. The extra build time is real. The cost of retrofitting it later is much more real.
If you’re mid-scoping and this decision is still open, talk to Amora about your build – it’s the kind of thing worth getting in front of before the first line of integration code gets written.
Got something you want built?
Amora Digital is an Australian software and AI agency. We scope it, build it, and ship it – live in 28 days. No offshore teams. No surprises.