| name | saas-integration-architecture |
| description | Design and build robust SaaS-to-SaaS integrations including API gateway patterns, webhook management, data transformation, sync strategies, and iPaaS alternatives for connecting enterprise applications. |
| license | Apache 2.0 |
| tags | ["saas","integration","api-gateway","webhooks","etl","ipaas","middleware","enterprise"] |
| difficulty | advanced |
| time_to_master | 12-20 weeks |
| version | 1.0.0 |
SaaS Integration Architecture
Overview
Modern businesses use 100-300 SaaS applications that need to exchange data reliably. SaaS integration architecture covers the patterns, protocols, and infrastructure needed to connect these applications — from simple webhook handlers to complex bidirectional sync engines. As AI agents gain the ability to operate across SaaS tools, robust integration architecture becomes critical.
When to Use This Skill
- Designing integration layers between multiple SaaS applications
- Building custom connectors when iPaaS tools are insufficient
- Implementing real-time bidirectional data sync
- Creating API gateways for unified access to multiple SaaS APIs
- Handling webhook management at scale (receiving, validating, processing)
Core Concepts
Integration Patterns
Point-to-Point Hub-and-Spoke Event-Driven
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ A │───│ B │ │ A │──┐ │ A │──publish──┐
└───┘ └───┘ └───┘ │ ┌───────┐ └───┘ │
│ │ │ ├──│ Hub │──┐ ▼
│ │ ┌───┐ │ │(iPaaS)│ │ ┌───┐ ┌──────────┐
└───────┘ │ B │──┘ └───────┘ ├──│ B │ │ Event │
┌───┐ ┌───┐ └───┘ │ └───┘ │ Bus │
│ C │───│ D │ ┌───┐ │ ┌───┐ └──────────┘
└───┘ └───┘ │ C │──────────────┘ │ C │ │
└───┘ └───┘ subscribe
O(n²) connections Centralized │
management ▼
┌───────┐
│Process│
└───────┘
Sync Strategies
| Strategy | Latency | Complexity | Best For |
|---|
| Polling | Minutes | Low | Low-volume, simple |
| Webhooks | Seconds | Medium | Event-driven updates |
| Change Data Capture | Sub-second | High | Database-level sync |
| Bidirectional Sync | Seconds | Very High | Two-way data flow |
| Batch ETL | Hours | Medium | Analytics, reporting |
Data Transformation Pipeline
Source API Transform Target API
───────── ───────── ──────────
{ "first_name": "Jane", ┌──────────┐ { "name": {
"last_name": "Doe", │ Map │ "first": "Jane",
"email": "j@co.com", │ Filter │ "last": "Doe"
"company": "Acme", ──►│ Enrich │──► },
"created": "2026-01-15" │ Validate│ "email": "j@co.com",
} └──────────┘ "org": "Acme",
"source": "crm",
"imported": "2026-03-31"
}
Implementation Guide
Webhook Management at Scale
import crypto from "crypto";
import { Queue } from "bullmq";
const webhookQueue = new Queue("webhooks", { connection: redisConnection });
app.post("/webhooks/:source", async (req, res) => {
const { source } = req.params;
if (!verifyWebhookSignature(source, req)) {
return res.status(401).send("Invalid signature");
}
await webhookQueue.add(source, {
source,
headers: req.headers,
body: req.body,
receivedAt: Date.now(),
}, {
attempts: 3,
backoff: { type: "exponential", delay: 5000 },
});
res.status(200).send("OK");
});
worker = (, (job) => {
{ source, body } = job.;
(source) {
:
(body);
;
:
(body);
;
:
(body);
;
}
}, { : redisConnection });
() {
secrets = {
: process..,
: process..,
: process..,
};
(source) {
:
stripe..(req., req.[], secrets.);
:
hmac = crypto.(, secrets.).(req.).();
crypto.(.(hmac), .(req.[]));
:
;
}
}
Bidirectional Sync Engine
class BidirectionalSync:
"""Sync records between two SaaS systems with conflict resolution."""
def __init__(self, source, target):
self.source = source
self.target = target
self.sync_state = SyncStateStore()
async def sync(self, object_type):
last_sync = self.sync_state.get_last_sync(object_type)
source_changes = await self.source.get_changes(object_type, since=last_sync)
target_changes = await self.target.get_changes(object_type, since=last_sync)
conflicts = self.detect_conflicts(source_changes, target_changes)
for change in source_changes:
if change.record_id not in conflicts:
await self.target.apply_change(change)
for change in target_changes:
if change.record_id not in conflicts:
await self.source.apply_change(change)
for conflict in conflicts:
resolved = self.resolve_conflict(conflict)
.source.apply_change(resolved)
.target.apply_change(resolved)
.sync_state.update_last_sync(object_type, datetime.utcnow())
():
conflict.source_modified > conflict.target_modified:
conflict.source_version
conflict.target_version
Data Mapping Framework
interface FieldMapping {
source: string;
target: string;
transform?: (value: any) => any;
required?: boolean;
default?: any;
}
class DataMapper {
constructor(private mappings: FieldMapping[]) {}
map(sourceRecord: Record<string, any>): Record<string, any> {
const result: Record<string, any> = {};
for (const mapping of this.mappings) {
let value = this.getNestedValue(sourceRecord, mapping.source);
if (value === undefined) {
if (mapping.required) throw new ();
value = mapping.;
}
(mapping. && value !== ) {
value = mapping.(value);
}
(value !== ) {
.(result, mapping., value);
}
}
result;
}
}
hubspotToSalesforce = ([
{ : , : },
{ : , : , : },
{ : , : , : },
{ : , : },
{ : , : },
{
: ,
: ,
: ({
: ,
: ,
: ,
: ,
: ,
: ,
}[stage] || ),
},
]);
Rate Limiting and Retry
class RateLimitedClient {
private queue: PQueue;
constructor(private maxPerSecond: number) {
this.queue = new PQueue({
concurrency: maxPerSecond,
interval: 1000,
intervalCap: maxPerSecond,
});
}
async request(config: RequestConfig): Promise<Response> {
return this.queue.add(async () => {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const response = await fetch(config.url, config);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "5");
await sleep(retryAfter * 1000);
continue;
}
response;
} (error) {
(attempt === ) error;
(.(, attempt) * );
}
}
});
}
}
Best Practices
- Idempotent operations — use external IDs to prevent duplicate records on retry
- Queue webhook processing — respond to webhooks immediately, process async
- Verify all webhook signatures — never trust unverified webhook payloads
- Implement circuit breakers — stop syncing if error rate exceeds threshold
- Log every transformation — maintain audit trail of data changes
- Handle pagination — never assume APIs return complete datasets
- Map fields explicitly — never auto-map; field names differ across platforms
Resources
Changelog
| Version | Date | Changes |
|---|
| 1.0.0 | 2026-03-31 | Initial documentation |