Integration work looks like plumbing and is not. The hard part is almost never calling the API. It is that the system on the other end models the world differently from yours, fails in ways you do not control, and occasionally contradicts a second system that is supposed to agree with it.
We are usually brought in when an organisation has three or four systems that each hold part of the truth, and no single place where those parts reconcile.
What we integrate
- Provider and vendor APIs, where the vendor’s model and your model disagree and the translation has to live somewhere deliberate.
- ERP and finance systems, typically older than everything around them, frequently the system of record, and rarely designed for the request volume a modern front end generates.
- Payment providers, usually several, for redundancy and market coverage.
- Regulatory and reporting endpoints, where the format is prescribed and the deadline is not negotiable.
- Data feeds, where volume is high, delivery is unreliable, and corrections arrive after the fact.
The decisions that determine whether it holds
One canonical model, providers behind adapters
Your system defines its own model of the entities that matter. Each external system gets an adapter whose only job is translating into it. Nothing downstream knows or cares which provider anything came from.
This is the decision everything else depends on. Without it, provider-specific handling leaks into business logic, and adding a second provider means touching every part of the system rather than writing one adapter.
Entity resolution is a first-class problem
The same real-world thing arrives from two systems with two unrelated identifiers, two spellings, and sometimes two slightly different timestamps. Matching them reliably cannot be done by name comparison alone.
What works is a persistent mapping between external identifiers and your canonical entities, seeded by automated matching and with anything ambiguous surfaced for a human decision rather than guessed. Once confirmed, a mapping is permanent, so the problem shrinks over time instead of recurring.
Assume redelivery, disorder and duplication
External systems retry. They redeliver after a timeout they saw and you did not. They send corrections hours later, and occasionally they send yesterday’s message again today.
Every one of those is normal operation, not an exception. Updates carry a sequence or revision, and anything older than current state is discarded by a conditional write rather than by application logic that races under load. Mutating operations are idempotent, so a retry cannot double-apply.
Push and pull get normalised early
Some systems stream over a persistent connection; others expose an endpoint to poll. These have completely different failure characteristics, and letting that difference propagate means every consumer has to understand both. Adapters convert either into the same internal event stream.
Recovery assumes disconnection
A dropped connection means missed messages, and simply reconnecting leaves a silent gap. The pattern that holds is snapshot plus delta: on reconnect, request full state, apply it, then resume the stream from the point that snapshot represents.
Staleness is monitored, not assumed
The dangerous failure is not a system going down, which is obvious. It is one that stays connected and quietly stops sending, so everything looks healthy while your data slowly goes wrong. Health is measured by expected message rate and time since last update, with alerting on silence rather than on errors.
What you end up with
Adding the next integration becomes an adapter and a mapping exercise rather than a change to your platform, and losing one during business hours becomes a failover rather than an incident.