The happy path is the part that writes itself
Connecting two systems on a good day is rarely the hard part. Authenticate, send a well-formed request, receive a well-formed response, store the result. That work is understood, and it is what a demonstration shows.
The engineering that determines whether an integration is dependable concerns the requests that do not go that way: the ones that time out, the ones that return a rate-limit response, the ones that succeed on the provider's side and fail on the way back, and the ones that arrive twice.
Idempotency is the decision that prevents the worst failures
If a request can be retried — and any request over a network can be — then the receiving side must be able to recognise that it has already handled it. Without that, a retry after a timeout creates a second order, a second charge, or a second record that someone reconciles by hand later.
This is a design decision rather than a library. It usually means the sender generates a stable key for the operation, and the receiver records that key with the result so a repeat returns the original outcome instead of performing the work again. It has to be decided before the integration is built, because retrofitting it means reasoning about every record created in the meantime.
Failures must be loud
The most damaging integration failure is not the one that throws an error. It is the one that quietly stops working — a credential expires, an endpoint moves, a schema gains a required field — and continues returning something that looks like a response while data stops flowing.
The remedy is unglamorous: log the failures, alert on the rate of them rather than only on individual ones, and treat a sustained absence of expected traffic as an alert in its own right. An integration that has processed nothing for a day may be correct, or may have been broken since yesterday morning, and only monitoring distinguishes those.
Isolate the provider behind something you own
Third-party APIs change on their own schedule. Fields are deprecated, versions are retired, terms are revised. When calls to a provider are scattered through an application, each of those events becomes a search-and-replace exercise across code that was written at different times by different people.
Putting the provider behind an interface the application owns turns that into a contained change in one place. It also makes the integration testable without the provider, which is what allows failure behaviour to be exercised deliberately rather than discovered.
Agree the system of record per field
When two systems both hold a customer address, one of them has to be authoritative — and it is worth deciding which, per field, in writing. Most reconciliation work exists because that decision was never made and both sides were allowed to write.
It is a dull conversation to have at the start of an integration and a very expensive one to have eighteen months in, when both systems contain months of divergent edits and no rule for which to believe.
The same discipline applies to deletion. If a record is removed in one system, the other has to be told something — deleted, archived, or flagged — and \"nothing\" is a decision too, usually the one that produces orphaned data nobody notices for a year.
Rate limits are a design input, not an error case
Most providers publish a rate limit, and most integrations discover it during the first bulk operation. Treating the limit as an exception to be caught means the integration works until the day it has real volume, which is usually the day it matters.
Designing for it instead means deciding up front how work is paced: whether requests are queued and drained at a known rate, whether a backlog is acceptable and for how long, and what the system does when the backlog grows faster than it drains. Those answers determine whether a busy day degrades gracefully or produces a queue nobody can clear.
Backoff belongs in the same conversation. Retrying immediately after a rate-limit response makes the situation worse for everyone using that provider, including the other parts of your own application.
Schema changes arrive without warning
Providers add fields, deprecate others, and occasionally change what an existing field means. An integration that parses strictly will break loudly on the first two; one that parses loosely may not break at all on the third, which is worse.
The practical position is to validate the fields you depend on, ignore the ones you do not, and log anything unexpected rather than discarding it silently. That combination survives additive change without pretending a semantic change did not happen.