viasocket
All articles
Action Layer for AI Products /October 12, 2026
image.png

Multi-Tenant Integration Architecture: How to Isolate Credentials, Routing, and Delivery Per Customer

4 min read

Most SaaS teams discover the multi-tenancy problem by accident. The first integration — say, a Slack notification or a HubSpot sync — works fine with one set of credentials and one webhook URL hardcoded into the config. Then a second customer signs up and wants to connect their own Slack workspace. Then a third wants Salesforce instead of HubSpot. Within a few months, the team is maintaining a patchwork of per-customer conditionals, shared tokens that were never meant to be shared, and a support queue full of "why did my data go to the wrong account" tickets.

This is the multi-tenant integration problem, and it's distinct from general multi-tenant application architecture. Most engineering teams have already solved tenant isolation for their core product — tenant_id on every row, scoped queries, row-level security. Integrations introduce a second, less obvious isolation problem: every tenant also owns a set of external credentials, external rate limits, and external failure modes that have to stay just as isolated as their data.

This article covers what actually needs to be isolated, the architecture patterns that scale, and where the trade-offs are.

#

Why integrations need their own isolation model

A multi-tenant integration layer has to isolate across at least four dimensions, and they don't all live in the same place in your stack:

Credentials. Customer A's HubSpot token must never be used to read or write Customer B's data. Obvious in principle, surprisingly easy to violate in practice once you're juggling encrypted tokens, refresh flows, and connection objects for hundreds of tenants across a dozen providers.

Routing. Customer A might route a lead.created event to HubSpot and Slack. Customer B routes the same event type to Salesforce and a custom webhook. Routing rules are tenant-specific configuration, not application code — which means your routing engine has to evaluate rules scoped to a tenant on every event, not just once at deploy time.

Delivery. If Customer A's webhook endpoint is down or their Salesforce account is rate-limited, their events should queue and retry independently of everyone else. A single noisy or broken tenant should never create head-of-line blocking for the rest of your customer base.

Observability. When something fails, you need to know which customer was affected, which destination failed, and which events didn't make it through. A shared, tenant-blind log is nearly impossible to debug at any scale past a handful of customers.

Get any one of these wrong and the failure modes are predictable: a credential cross-contamination bug that leaks one customer's data into another's connected app, a shared job queue where one tenant's rate-limited API starves everyone else's deliveries, or a 2 am incident where you can't tell which of 400 tenants is actually affected.

#

Pattern: workspace-per-tenant, not infrastructure-per-tenant

The architecture that scales for most B2B SaaS products is a workspace-per-tenant (or namespace-per-tenant) model, built on shared infrastructure with strict logical isolation — not separate databases, queues, or compute per customer, which gets prohibitively expensive past a few dozen tenants.

The idea: every tenant gets a logical workspace that owns its own connections, routing rules, and event history. Everything inside a workspace is isolated from every other workspace, but the underlying event bus, delivery engine, and credential store are shared infrastructure.

Tenant A -> Workspace A: Connection: HubSpot (Tenant A's OAuth token), Connection: Slack (Tenant A's webhook), Rule: lead.created -> HubSpot, Slack Tenant B -> Workspace B: Connection: Salesforce (Tenant B's OAuth token), Rule: lead.created -> Salesforce

In practice, this means:

  • One event bus, tenant-tagged events. Every event carries a workspace/tenant identifier. The routing layer only evaluates rules scoped to that identifier — it never has visibility into another tenant's rules.

  • One delivery engine, per-connection retry state. Retry counters, backoff state, and dead-letter queues are tracked per connection, not globally. A failing connection for Tenant A should never block or throttle Tenant B's deliveries.

  • One credential store, scoped access. Credentials are stored centrally (encrypted at rest), but every read is scoped to a specific workspace + connection ID. There is no code path that can fetch "a HubSpot token" without specifying whose.

This gives customers a clean mental model too — "here is my workspace, my connections, my delivery logs" — without you having to provision and maintain separate infrastructure per customer. It's also the pattern most engineering teams converge on independently: Azure's multitenant architecture guidance recommends the same anti-corruption and bulkhead patterns per tenant integration, and most production SaaS platforms land on a workspace or namespace abstraction for exactly this reason.

#

Credential management is the highest-risk part

Of the four isolation dimensions, credentials deserve the most deliberate design, because a mistake here is a security incident, not a bug ticket. A few patterns that hold up in production:

Encrypt at rest, scope on every read. Store tokens encrypted. When the delivery engine needs a credential, it fetches by (workspace_id, connection_id) — never by provider type alone, and never in a way that a missing filter could silently return the wrong tenant's token.

Treat OAuth as a lifecycle, not a one-time event. Every OAuth-based connection needs refresh handling, expiry detection, and a defined behavior when a customer revokes access from the provider's side. This has to be designed per-tenant: a stale refresh token for Tenant A shouldn't trigger alerts or retries that touch Tenant B's connection to the same provider.

Decide identity ownership deliberately. Multi-tenant integrations get more complicated once AI agents enter the picture, because now you're choosing between a shared service account and a per-tenant, per-user OAuth grant for the same connection. That decision has real consequences for blast radius if a credential or a prompt is compromised — it's worth treating as its own architectural decision rather than defaulting to whatever is fastest to wire up.

Let customers rotate their own keys where possible. For API-key-based providers, giving tenants a UI or API to rotate their own credentials reduces your support burden and keeps key ownership where it belongs — with the tenant, not buried in your support team's inbox.

Audit every credential access. Log who accessed which credential, when, and for which delivery. Without this, a compromised token is undiagnosable — you won't know what it touched.

#

Routing and rate limits also need a tenant boundary

Credential isolation gets most of the attention, but routing and rate-limit isolation cause just as many production incidents.

Routing rules are customer-owned configuration. If Tenant A reconfigures their event routing, that change must never be globally cached or evaluated against Tenant B's events — a routing engine that caches "the current rule set" without a tenant key is a bug waiting to ship.

Rate limits are where multi-tenant integrations get genuinely hard, because the constraint isn't yours — it's the third-party provider's. If ten of your tenants all connect to the same Slack workspace API, or you're hitting a shared app-level rate limit on a provider, one tenant's burst of activity can degrade delivery for everyone else sharing that quota. The fix is to track rate-limit budgets per connection (and, where the provider allows it, request per-tenant API credentials rather than a single shared app-level integration) so that one customer's traffic spike queues against its own budget instead of borrowing capacity from someone else's.

#

When workspace-per-tenant isn't enough

Logical isolation on shared infrastructure is the right default, but it isn't the end state for every customer segment. Enterprise customers with strict compliance requirements sometimes need:

  • Dedicated credential storage (separate encryption keys or a separate secrets namespace per enterprise tenant)

  • Dedicated queues or workers for tenants whose volume or SLA requirements would otherwise compete with the rest of your customer base

  • Regional isolation, where a tenant's integration traffic and data residency need to stay within a specific region or cloud boundary

The right approach is to design the workspace abstraction so that these upgrades are a configuration change — assigning a workspace to a dedicated queue or a dedicated secrets namespace — rather than an architectural rewrite. If your tenant model is clean from the start, "give this one customer dedicated infrastructure" becomes a deployment decision, not a re-platforming project.

#

Where this fits for teams building the integration layer themselves

Everything above is solvable in-house, and plenty of teams do solve it — but it's also exactly the kind of infrastructure that compounds in cost the more integrations and tenants you add. Credential lifecycle management, per-tenant rate-limit tracking, retry/dead-letter isolation, and tenant-scoped observability are not integration-specific problems you solve once; they're a layer of plumbing that has to be maintained correctly for every provider and every customer, indefinitely.

That's the problem an embedded integration layer like viaSocket is built to take off a product team's plate: workspace-scoped connections, per-tenant credential isolation, and delivery/retry handling that's already built for multi-tenant SaaS, so your team can focus on which integrations your product needs rather than re-deriving the isolation guarantees underneath them. The architecture decisions above don't change if you build this yourself or embed it — but they're worth getting right either way, because they're the difference between an integration feature that scales cleanly to your next thousand customers and one that needs a rewrite at your two-hundredth.