← Back
Home › Systems › IT operations, analytics and integration

ipaas

integration platform connecting SaaS apps

Zapier, Workato, MuleSoft

Open the ipaas pageipaas Pratt parsingcompilers: Table-driven expression parsing with one loop and a numeric binding power per operator; the compact core of precedence handling in hand-written parsers.›Template enginescompilers: Compile templates with loops, partials and auto-escaping into render functionsHdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDAG workflow executiondistributed: Run a graph of dependent tasks by releasing each one when its inputs are done, longest chain first, and skip any task whose input hashes are unchanged.›Dead-letter queuedistributed: A side queue for messages that keep failing: after N delivery attempts or a permanent error, park them with the reason so the main queue keeps moving.›Deterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Durable workflow executiondistributed: Ordinary code made crash-proof by logging every side-effect result and replaying the code from that log; activities run at least once, workflow logic must be deterministic.›Event deduplication windowdistributed: Keep the ids of recently accepted events for a time window and drop repeats; memory stays bounded by rotating time buckets instead of per-entry timers.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›Idempotency keysfinance: Client-chosen unique key per logical operation, stored with a request fingerprint and the final response, so retries replay the first result instead of acting twice.›Process supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›Cursor paginationnetworking: Keyset/opaque-cursor pagination for stable incremental reads from APIs and tablesWebhook deliverynetworking: Reliable server-to-server HTTP callbacks: signed POSTs, 2xx-means-done, retries over days with jitter, endpoint disabling, SSRF guards and idempotent receivers.›Coverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›Hmac request signingsecurity: HMAC canonical-request signatures and presigned URLs (SigV4 style) with expiryJSON Schema validationsecurity: Compile a JSON Schema once, then check every untrusted document against it, within hard size and depth limits, and report failures as JSON Pointers.›Mutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›OAuth 2 and OpenID Connectsecurity: Delegated authorization (OAuth 2) and login on top of it (OIDC); use authorization code + PKCE for every user-facing client.›Process sandboxingsecurity: Run risky work in a separate, deliberately powerless process and let a small privileged broker do the few things it needs, over a narrow pipe.›Secrets managementsecurity: Keep keys and passwords out of code and logs, give workloads short-lived identity instead of static keys, and make rotation routine.›Point-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›Schema inferencestorage: Infer column types and structure from sample recordsCron schedulingsystems: Cron expression parsing and next-fire-time computation, with catch-up and time-zone rulesExponential backoff with jittersystems: Retry after delays that grow geometrically and are randomized, so failed clients spread out instead of hammering a recovering service in synchronized waves.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Token bucketsystems: A bucket of up to b tokens refilled at rate r; requests spend tokens, enforcing an average rate while allowing bursts of b.›Name resolutioncompilers: Bind every use of a name to its declaration with a scope stack, handling shadowing, hoisting and closure capture in one AST walk.›Recursive-descent parsingcompilers: Hand-written top-down parser with one function per grammar rule; simple, fast, with full control over error messages and recovery.›Html sanitizationsecurity: Allowlist-based HTML sanitizing of untrusted markupTopological sortgraphs: Order the vertices of a DAG so every edge points forward; the core of build systems, schedulers and dependency resolution, with cycle detection for free.›Content-addressed storagestorage: Store each blob under the hash of its bytes: immutable, deduplicated by construction, self-verifying on every read, and composable into Merkle trees.›Cooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerFault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildEvent sourcingfinance: Persist state changes as an append-only stream of immutable events per entity; current state is a fold over them, guarded by expected-version appends.›Hierarchical timing wheelsystems: Timers bucketed by expiry into wheels of slots, coarser wheels for far deadlines: O(1) insert and cancel for millions of timeouts.›Durable job queuedistributed: Persisted tasks that workers lease for a visibility timeout, ack on success and retry with backoff; at-least-once execution that survives worker and queue crashes.›Message sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›+ 20 moreBFS and DFSgraphs: The two basic graph traversals; BFS gives hop distances and levels, DFS gives discovery/finish order for cycles, components and topological order.›Merkle dagdata-structures: Content-addressed DAG of hashed nodes (git trees/commits) for dedupe and verifiable historyAtomic file replacementhardware-os: Write temp file, fsync, rename over target, fsync directory: crash-safe whole-file updatesTransparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelOptimistic concurrency controlstorage: Run transactions without locks, record what they read, and validate at commit that nothing changed; fastest when conflicts are rare.›Slab allocatorsystems: Pre-carve pages into equal-size slots per size class with an in-place free list: O(1) alloc and free, no external fragmentation, cache-warm reuse.›+ 9 moreSparse matrices (CSR)numerics: Compressed sparse row stores only nonzeros as row pointers, column indices and values; SpMV streams them once, so it is memory-bandwidth bound.›Ebpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signals+ 5 more

How it is built

Architecture overview

An integration platform connects SaaS applications to each other without the customer writing a service. A customer defines a flow ("when a deal is won in the CRM, create a customer in billing and post to chat") and the platform runs it for them, many thousands of times a day, against APIs it does not control. The product is the library of connectors and the runtime that makes a flow of unreliable steps behave like a reliable one.

The one idea that makes it work is that every external effect is a recorded step in a durable run. A trigger (an inbound webhook or a poll of a remote API) becomes a persisted event; the event starts a run; each step's result is written down before the next step starts; a worker that dies is replaced and the run resumes from the log, not from the beginning. Everything else follows from that: retries are safe because steps carry idempotency keys, slow or failing vendors are isolated by queues and breakers, and customer-written code runs in a sandbox because the platform executes strangers' logic next to other tenants' secrets.

The platform is two planes. The control plane stores flow definitions, connections, credentials and schedules, and serves the editor. The data plane ingests events, executes runs and delivers results. They share a database but scale separately: the control plane is read-mostly and small, the data plane carries the load.

Parts:

 SaaS vendors                                      SaaS vendors
  (webhooks) ───►┌────────┐                     ┌─►(REST calls)
                 │ Ingest │──events──┐          │
  (polled) ◄────►│ + poll │          ▼          │
                 └────────┘     ┌─────────┐  ┌──┴─────────┐
                                │  Event  │  │ Outbound   │
   Control plane                │  store  │  │ policy     │
  ┌───────────────┐  flow defs  │ (WAL)   │  │ rate limit │
  │ flows, creds, │────────────►└────┬────┘  │ breaker    │
  │ schedules     │                  │       └──▲─────────┘
  └──────┬────────┘            ┌─────▼──────┐   │ step calls
         │ secrets             │ Run engine │───┘
         ▼                     │ (durable)  │──► Sandbox (code steps)
  ┌───────────────┐            └─────┬──────┘
  │ Secrets/OAuth │                  │ steps as jobs
  └───────────────┘            ┌─────▼──────┐
                               │ Job queue  │──► workers ──► DLQ
                               └────────────┘

How the parts fit together

Ingest: events in

Vendors tell the platform about changes in two ways, and the platform must support both. Push vendors call a platform URL; the receiver does three things only: verify the signature (hmac-request-signing (no page yet)), append the raw body to the event store, and answer 2xx. Doing more before answering invites the vendor's retry, so mapping and routing happen later from the stored event. This is the receiving half of Webhook delivery, and the dedupe rule is the same as the sender's: the vendor will redeliver, so the receiver keeps recent event ids in Event deduplication window and treats a second arrival as a no-op.

Pull vendors have no push, so the platform polls. The contract is the one in Incremental sync cursors: store one opaque cursor per (connection, trigger), ask for changes since it, process, and only then advance it. Advancing after processing gives at-least-once delivery; the dedupe step turns that into effectively-once.

cursor-pagination (no page yet) is how the poller reads a vendor's list endpoint when a change set spans several pages: it follows the vendor's opaque next-page token until the page is empty and only then moves the stored sync cursor, so a crash mid-listing re-reads pages instead of skipping them. Offset paging is avoided because records inserted during the walk would shift the pages; the pages go into the event store as ordinary events, with the dedupe step absorbing the overlap.

Run engine: a flow is a durable run

Two models compete. Durable workflow execution runs ordinary code and replays it from a log of completed step results after a crash; it suits flows with loops, branching on data and long waits. DAG workflow execution holds the flow as a graph of steps with declared inputs and releases a step when its inputs are done; it suits visual builders where the editor already draws a graph, and it makes parallel branches and "skip downstream of a failure" cheap to express. Platforms with a canvas editor tend to store a graph and execute it with a small replay log; platforms that expose code store the code. Both persist the run as an append-only history (Event sourcing), which doubles as the run-history screen customers debug with.

Long delays ("wait three days, then check") are timers, and millions of them are cheap in a Hierarchical timing wheel rather than a sleeping thread each. Scheduled triggers use cron-scheduling (no page yet); the hard parts are time zones, daylight-saving gaps and runs missed during an outage, so skip, run-once or run-all is a per-flow setting.

The contract between engine and queue: the engine emits "run step X of run R, attempt N" as a job and receives either a result or a failure. It never calls a vendor itself.

Queue and workers: where retries live

A step becomes a row in a Durable job queue. A worker takes it under a Leases visibility timeout, executes, and acknowledges; a worker that dies lets the lease lapse and another picks the job up. Failure policy is split by cause. Transient errors (timeouts, 429, 5xx) retry on Exponential backoff with jitter, honouring the vendor's Retry-After when given. Permanent errors (400, 401 after a refresh attempt, 404) stop retrying at once. After a bounded number of attempts the job goes to a Dead-letter queue with its reason, and the run is marked failed and visible to the customer, who can fix the data or credential and replay. Lease expiry means a step can run twice, so the queue's contract is at-least-once and the call policy below must make that harmless.

Outbound call policy: being a good citizen of other people's APIs

The platform makes calls for thousands of customers to a few hundred vendors, and most vendors limit per account. Rate limiting is therefore per connection, not per platform: a Token bucket per (vendor, connection) allows the vendor's burst and average,. When a vendor is down, a Circuit breaker per vendor stops workers from burning attempts and holding connections; open means jobs for that vendor wait in the queue instead of failing, which keeps the dead-letter queue for real errors.

The write side needs Idempotency keys. Creating an invoice twice because a lease expired is the failure customers notice. Where the vendor accepts a key, the platform sends a key derived from (run id, step id), which is stable across attempts. Where it does not, the step performs a lookup-then-create keyed on a natural field, and the connector documents the remaining window honestly.

Connections: OAuth and secrets

Most connectors authenticate with delegated access, so the platform is an OAuth client for hundreds of providers (OAuth 2 and OpenID Connect). It stores a refresh token per connection, refreshes access tokens ahead of expiry, and handles the refresh token rotating or being revoked, which surfaces as a "reconnect" task for the customer instead of a failing run. Tokens and API keys are encrypted at rest and decrypted only inside the worker for the duration of a call (Secrets management); the editor and logs only ever see a reference.

Not every connection is an OAuth grant. Some vendors and customers' private systems accept only a client certificate, and the platform's own internal hops (engine to queue, workers to secrets store) should not share bearer tokens. Mutual TLS covers both: the certificate and key are stored as a connection secret like any refresh token and presented at call time by the outbound client, and inside the platform each service's certificate identifies it, so a compromised worker cannot impersonate the engine.

Mapping: the data between two schemas

The editor's field mapping ("{{trigger.contact.email}}") is a small expression language compiled once per flow version. Pratt parsing covers the expression grammar; a template-engines (no page yet) layer renders strings with loops and filters. Before a call, the payload is checked against the connector's schema (JSON Schema validation), which turns a vendor's cryptic 422 into a local error that names the field.

When a connector has no published schema, schema-inference (no page yet) derives one from sample responses captured during connection testing: field names, types, which fields are sometimes missing or null, and which are arrays. The editor uses it to offer the field picker, and the validation step before a call compares live payloads against it, reporting drift as a mapping warning rather than a failed run.

Sandbox: customer code

Code steps (JavaScript, scripts) run strangers' logic beside the secrets of other tenants. Process sandboxing gives the shape: a deliberately powerless child process, a small privileged parent that brokers the few operations it may do (HTTP out, read its own input), and a narrow pipe between them. The child's limits come from Cgroups and namespaces (CPU, memory, pids, private network and filesystem view) and a syscall allow-list (seccomp-bpf (no page yet)). The platform assumes the sandbox will eventually be escaped, so the sandbox host holds no standing credentials; a microVM or V8 isolate trades start-up time against isolation strength.

State and recovery

The event store and run history are the system of record, so they follow database rules: a Write-ahead log before data pages, Primary-backup replication with a defined acknowledgement rule so a primary loss does not lose acknowledged events, and Point-in-time recovery for the day an operator or a bug corrupts data. Large payloads live in Object storage; the run history keeps a reference with the content hash. Retention is a product decision: replays are limited to retained runs.

Operations and assurance

Deploys must not cut runs in half. On SIGTERM a worker follows Graceful draining: stop leasing, finish or checkpoint in-flight steps, release the rest.

Deterministic simulation testing runs the queue, engine and a faked network and clock in one seeded thread, so "worker dies between the vendor call and the ack" is a reproducible test instead of a monthly incident, with fault-injection (no page yet) supplying the failures. Coverage-guided fuzzing targets the parsers that face the outside world: expression language, template engine and webhook payload decoders.

Process supervision restarts the receivers, engine and workers when they die; combined with leases on queued steps, a crashed worker's steps return to the queue without anyone being paged. hdr-histogram (no page yet) records the delay from webhook receipt to step start and the duration of each vendor call, so the 99th and 99.9th percentiles can be reported per connector and per tenant; that is how a slow vendor is told apart from a saturated queue.

Data and control flow

Flow 1: an inbound webhook becomes a vendor write

  1. The CRM POSTs "deal won" to /hooks/<flow-token>. The receiver verifies the signature, appends the raw event to the store, and returns 2xx. Time here is one durable write, so it is bounded by the log's fsync (order of a millisecond on SSD).
  2. The dedupe check drops it if the vendor's event id was seen in the window.
  3. The engine loads the flow version that was current when the event arrived, creates a run, and enqueues step 1.
  4. A worker leases the job, decrypts the connection's token, evaluates the mapping expressions, validates the payload against the schema.
  5. The outbound policy takes a token from the (vendor, connection) bucket; if the breaker is open the job goes back to the queue with a delay.
  6. The worker calls the vendor with an idempotency key (run id, step id), writes the response into the run history, and acks the job.
  7. The engine reads the new history, releases the next step; the run ends when the last step completes. Most of the elapsed time is vendor latency, not platform work.

Flow 2: polling a vendor without push

  1. The scheduler fires the trigger for a connection (every N minutes by plan).
  2. A worker reads the stored cursor and requests changes since it, with the connection's rate limit applied.
  3. For each returned record it writes an event keyed by the vendor id and version; duplicates collapse in the dedupe step.
  4. Only after the events are durable does it store the new cursor. A crash before this step re-reads the same page, which dedupe absorbs.
  5. If the vendor reports the cursor expired, the trigger falls back to a bounded rescan and tells the customer.

Flow 3: a failing step

  1. The vendor returns 503. The worker records the attempt and releases the job with a backoff delay with jitter.
  2. After repeated failures the circuit breaker for that vendor opens; other customers' jobs for that vendor wait instead of failing.
  3. When the breaker closes, queued jobs drain at the bucket rate, not all at once.
  4. A job that exhausts its attempts moves to the dead-letter queue; the run shows "failed at step 3" with the vendor's last response, and a replay button re-enqueues from that step.

Scaling

In the order you hit them:

  1. Vendor rate limits, not CPU. Per-connection buckets and fair queueing across tenants stop one customer's backfill from starving the rest. The lever is shaping, not hardware.
  2. Queue and run-history writes. Every step is several durable writes. Batch the log writes (group commit), keep payloads out of the hot rows by storing large bodies in object storage, and partition queues by tenant or flow so lease contention stays local.
  3. Ingest bursts. A vendor that replays a day of webhooks arrives as a spike. The receiver does one append, so scaling is stateless horizontal nodes in front of the log, with the queue absorbing the burst.
  4. Sandbox density. Process sandboxes start in milliseconds and share a kernel; isolates are denser still; microVMs cost more per tenant but isolate harder. Pick by trust in the code, not by throughput.
  5. History growth. Retention windows and moving old runs to cold object storage keep the hot database small.

Failure modes

FailureSymptomCauseFix
Duplicate records in the targetTwo invoices for one dealLease expired mid-call, step retried, vendor had no idempotency supportDerive a stable key per (run, step); lookup-then-create where keys are unsupported
Missed eventsDeal won but no flow ranWebhook lost while receiver was down; poll cursor advanced before processingPoll as a backstop for push; advance the cursor only after durable write; gap alarm on sequence numbers
Retry storm after a vendor outageVendor returns 429 for hours after recoveryAll queued jobs retried at onceBackoff with jitter, per-connection bucket, breaker half-open probes
Token silently deadEvery run for one customer fails with 401Refresh token revoked or rotated and not storedPersist rotated tokens atomically; raise a reconnect task, pause the flow
Sandbox escape or noisy tenantOther tenants slow or exposedCode step with too many privileges or no cgroup limitsMemory/CPU/pid limits, seccomp, no ambient credentials, separate host account
Run stuck "running"No progress, no errorWorker died after taking the lease; lease never reclaimed, or timer lostLease timeouts; sweeper for runs without a live job; timers persisted, not in memory only
Replay differs from originalResumed run takes a different branchNon-deterministic code in a replay-based engine (time, random, direct I/O)Route effects through recorded steps; ban ambient time and randomness in flow code

Real implementations

namewhat it isrepolicencenotes
Apache CamelIntegration framework implementing the enterprise integration patterns, with hundreds of componentsapache/camelApache-2.0Library you embed; routes are code or DSL; no hosted control plane
Node-REDBrowser-based flow editor on Node.js for wiring APIs and devicesnode-red/node-redApache-2.0Graph runtime in one process; persistence and scaling are the operator's job
HuginnSelf-hosted agents that watch sources and act, chained by eventshuginn/huginnMITPoll-oriented; small scale
ActivepiecesSelf-hostable no-code automation with a connector ("piece") libraryactivepieces/activepiecesMIT outside ee directoriesEnterprise code is under a separate commercial licence
n8nWorkflow automation with a node library and code nodesn8n-io/n8nSustainable Use License (source-available); .ee. files separately licensedNot an OSI open-source licence; check terms before embedding
WindmillScripts and workflows as jobs on a Postgres-backed queuewindmill-labs/windmillApache-2.0 and AGPL-3.0 by directory, plus proprietary partsPer the repo's LICENSE file; check the directory you use
TemporalDurable-execution engine with replay from event historytemporalio/temporalMITThe run engine and queue only; no connectors
Apache NiFiData-flow server with back-pressure and per-record provenanceapache/nifiApache-2.0Closer to data movement than SaaS actions
SvixWebhook sending service: signing, retries, endpoint managementsvix/svix-webhooksMITThe outbound-delivery seam as a product

Commercial platforms of this kind, such as Zapier, Workato and MuleSoft, are not open-source; they are named only as examples of the category.

Sources

  • Hohpe, Woolf, Enterprise Integration Patterns, Addison-Wesley 2003. site
  • Standard Webhooks specification (signing and delivery conventions). repo
  • RFC 2104, HMAC: Keyed-Hashing for Message Authentication. rfc
  • RFC 6749, The OAuth 2.0 Authorization Framework. rfc
  • Project documentation and LICENSE files of the repositories in the table above.

Similar systems

Share the most building blocks with this one.