Three sources, none of them wrong
The integration specification I spent the most time with in 2025 was a PDF, last revised in 2016, describing a fixed-width file format delivered over SFTP. It had a field marked "reserved for future use" that turned out to contain something important, and a sample file whose column offsets did not match the table on page nine.
This is normal. If you're coming from consumer web work where every integration is a JSON API with a decent SDK and a status page, financial infrastructure is a genuine adjustment. The systems are old, they're load-bearing for the entire industry, and their conservatism is mostly justified. You will not be changing them. The engineering problem is entirely on your side of the line.
Land raw, transform separately
The one architectural rule I'd keep if I could keep only one: nothing from an external system writes directly into a domain table.
Every payload lands raw first. The exact bytes of the file or the response body, a hash, the source, when it arrived, and a status. Parsing and transformation happen afterwards as a separate step reading from that store. It costs a table and some disk and it has paid for itself repeatedly.
It pays off when a provider changes a format without telling you, because you can reparse history instead of asking them to resend six months of files. It pays off when your own transformation has a bug, for the same reason. And it pays off in an audit, when somebody asks what a registrar actually told you on a date eighteen months ago and the honest answer needs to be the payload rather than your interpretation of it.
We keep raw payloads indefinitely. Storage is the cheapest thing in the system and it's the only copy of the truth that isn't downstream of our own code.
Webhooks are a hint, not a delivery guarantee
Most providers that offer webhooks describe them as notifications, and the good ones mean it literally.
Deliveries arrive out of order, arrive twice, and sometimes don't arrive. So a webhook handler does two things and no more: verify the signature, and enqueue a job to go and fetch the current state of whatever the event referred to. It never trusts the body of the notification as data. If two notifications for the same entity arrive out of order, both jobs fetch current state and both reach the same answer, which makes ordering irrelevant instead of a problem to solve.
The backstop is a poll. Whatever the webhooks cover, there's also a scheduled reconciliation sweep that fetches everything changed since the last successful sweep and picks up whatever the notifications missed. Every provider I've integrated with has dropped events. None of them advertise it, and you don't find out from an error, you find out from a customer.
Retries, when the operation moves money
Standard retry advice assumes idempotent reads. Outbound writes into financial systems are a different problem, because the failure mode isn't a slow response, it's a request that succeeded on their side and timed out on yours.
Retrying blindly submits the order twice. Not retrying leaves you not knowing whether it went through at all, which is worse.
So every outbound write carries a client-generated reference, and the retry path is query-then-write rather than write-again: look up the reference on their side, and only submit if it isn't there. Providers vary in how well they support this. Where a provider has no lookup by client reference, the operation stops being automatic. It goes into a queue for a human, with the timestamp and the payload, because a person checking a portal is slow and correct, and a retry loop is fast and occasionally catastrophic.
Completeness is not a field
A subtle one that caused us real trouble: a feed that is incomplete at four in the afternoon looks exactly like a feed that is complete at six.
You get a file, it parses, every row is valid, and nothing about it announces that another forty per cent of the day's records are still coming. If your valuation job runs on a schedule and the data happens to be partial, you produce confidently wrong numbers.
Where a provider gives you a control record with an expected count, use it and reject the file if it doesn't match. Where they don't, you infer completeness, and you say so: data is marked provisional until whatever heuristic you're using is satisfied, and anything computed from provisional data is flagged as such in the interface. Advisors are entirely comfortable with "provisional, awaiting full feed". They are not comfortable with a number that changes overnight with no explanation.
When two authoritative systems disagree
Here's the part that took a real decision rather than an engineering answer.
For a given holding we may have three views: what our own ledger computes from transactions we've processed, what the registrar's statement says, and what the custodian reports. They disagree regularly, and none of them is wrong exactly. They're describing the same position at different points in a settlement cycle, with different cut-off times.
The trap is trying to resolve this in code with a cleverness heuristic. What it actually needs is a written precedence policy, decided by people who understand the domain rather than by whoever is writing the merge function.
Ours says the registrar is authoritative for unit counts, the valuation agency is authoritative for prices, and our own ledger is authoritative for nothing at all. Our ledger's job is to predict what the registrar will say and to raise a break when it doesn't. That framing changed how the team thought about the system: we're not the source of truth, we're a fast, provisional model of it, and every discrepancy is either a defect in our model or a genuine timing difference, and it has to be classified as one or the other.
Which is really the whole thing. You cannot make an external system reliable, and you shouldn't try. You can be explicit about what you know, when you knew it, who told you, and how confident you are. Everything else in the integration layer is bookkeeping around that.