Agents

Building 500+ Integrations Without Going Insane

A look inside our integration architecture—what worked, what broke, and the OAuth flow that still haunts our dreams.

February 17, 2026
7 min read
Jack Jundanian

The Integration Challenge

When we decided Billix would connect to "every tool your team uses," I don't think anyone on the engineering team fully grasped what that meant. Five hundred services. Each with its own API, its own authentication flow, its own rate limits, its own quirks, and its own definition of what a "contact" or "task" or "message" is.

We learned fast that building integrations isn't really an engineering problem. It's a taxonomy problem. Everyone's data looks different, and your job is to make it all feel the same to the user.

Our Architecture Decisions

Early on, we made three bets that paid off:

  • Adapter pattern over direct integration. Every service connects through a normalized adapter layer. The adapter translates service-specific concepts into our internal model. Gmail's "threads" and Slack's "conversations" both become "message groups." HubSpot's "deals" and Salesforce's "opportunities" both become "pipeline items."
  • Async execution everywhere. API calls fail. A lot. We designed every integration call to be retriable and idempotent from day one. If a request to the Google Calendar API times out, we retry with exponential backoff. The user never sees the failure.
  • Schema versioning per service. APIs change. Google alone has deprecated three different authentication methods since we started. Every adapter tracks which API version it's using, and we can roll updates without touching the core platform.

Here's what the adapter layer looks like in practice:

  • Gmail calls them "threads" → we normalize to message groups
  • Slack calls them "conversations" → same thing, message groups
  • HubSpot calls them "deals" → we normalize to pipeline items
  • Salesforce calls them "opportunities" → also pipeline items
  • Asana calls them "tasks" → we normalize to work items
  • Linear calls them "issues" → also work items

Same concepts, different names. Our adapter layer is the Rosetta Stone.

Handling Edge Cases

For every integration that works smoothly, there are five that have weird edge cases. Some highlights from our incident log:

One CRM's API returns dates in three different formats depending on which field you're reading. Another service rate-limits you to 2 requests per second but doesn't tell you that in their documentation—we found out the hard way when a user tried to sync 1,200 contacts.

My personal favorite: a project management tool that returns 200 OK status codes for failed requests. The actual error is buried six levels deep in a nested JSON response. We spent two days debugging that one before a junior engineer found it by accident.

The OAuth Dance

OAuth was supposed to make authentication simple. In practice, it's the single biggest source of support tickets we get. Not because our implementation is bad—it's because every service implements OAuth slightly differently.

Some use OAuth 2.0 with PKCE. Some still use OAuth 1.0a. Some require you to refresh tokens every hour. Some issue tokens that last a year. One service (I won't name names) requires you to re-authenticate if you change your password anywhere in their ecosystem, including services that have nothing to do with the API you're using.

We built a token management system that handles all of this transparently. It watches for token expiration, handles refresh flows, and falls back gracefully when things go wrong. The user connects once and forgets about it.

Our OAuth incident log reads like a horror novel:

"Token expired 47 minutes into a 60-minute window. Docs say tokens last 3600 seconds. They last 2820. Filed a bug with the vendor. They closed it as 'working as intended.'"

"Service requires re-auth after password change. User changed their password on an unrelated product in the same ecosystem. Our integration silently broke. Took us three days to reproduce."

You can't make this stuff up.

What We'd Do Differently

If I were starting over, I'd invest more in integration testing early. We wrote unit tests for our adapters, but the real bugs only show up when you're talking to the actual API with real data. We now run a full integration test suite against sandbox accounts for our top 50 services every night.

I'd also push harder on webhooks from the start. Polling for changes is expensive and slow. Most modern services support webhooks, and the ones that don't usually have workarounds. We migrated to a webhook-first architecture about eight months in, and the reduction in API calls (and our bill from various services) was dramatic.

Building integrations at scale is grunt work. There's no magic architecture that makes 500 APIs easy. But with the right patterns and a healthy respect for how weird third-party APIs can be, it's very doable.

Let's Make Life Easier

Try Billix for
free right now

Start for free

Enterprise-
Grade Security

Effortless
For Everyone

Automation
Made Natural

Unified
Workflow Sync

Start for free, right now