Connecting two systems: when you need a custom API
Almost every company we talk to has the same scene running, even if they describe it differently. There is one system where customers come in and another where they get invoiced. Between the two sits a person exporting a CSV every Friday, or an intern retyping numbers, or an automation someone built three years ago that nobody dares touch because it half works and it is frightening.
What follows is always the same: the data drifts apart, someone invoices against stale information, and when you ask for a consolidated report the answer is “depends which system you look at”. Integrating those two systems is the obvious fix. What is not obvious is when an off-the-shelf connector is enough and when you need to build a custom API — a decision worth several thousand euros that is almost always made on the wrong information.
What integrating two systems actually means
The naive version of an integration is “system A sends its data to system B”. With that definition, any marketplace connector looks sufficient and any development quote looks expensive.
The real version includes three questions the connector will not answer for you. First, who owns each field: if a customer’s phone number changes in the CRM and also in the ERP, which one wins? Second, what happens when it fails: if system B is down for three hours, are the changes lost, retried, or queued? Third, how do you find out: when the sync stops working on a Tuesday, do you catch it with an alert, or does your customer catch it when the wrong invoice lands?
A standard connector solves the happy path. Real integration is everything else — and everything else is where the money goes.
Real scenarios we run into
The connector covers 80% and the missing 20% is your business
This is the most common case and the most frustrating one. The official connector between the CRM and the ERP syncs customers, products and orders. Great. Except you happen to have volume discounts calculated with your own rule, or an approval flow when an order goes over a certain amount, or three customer types that map to a single field in the ERP.
That 20% is not a whim: it is precisely what makes you different from your competitors. And it is exactly what no generic connector will ever cover, because it is built for the common denominator of thousands of companies.
The two systems disagree about the same customer
Here the problem is not technical, it is the data model. In the CRM a customer is a company with several contacts. In the ERP it is a tax ID with a billing address. In the support tool it is an email address. All three are “the customer” and none of them line up.
Before a single line of integration gets written, someone has to decide the mapping — and that decision does not belong to the developer, it belongs to the business. When that conversation gets skipped, the integration works beautifully in testing with clean data and breaks on day one with real data.
The sync is fine until someone edits on both sides
A one-way sync is a plumbing problem. A two-way sync is a concurrency problem, and those are different animals.
We have inherited syncs that looped: system A updates B, B sees a change and updates A, and around it goes until the API quota is exhausted by eleven in the morning. The fix is not one more if. It is designing from the start with origin markers and last-write tracking, so each system can tell a real change from its own echo.
The legacy system has no API
This happens more than you would expect, especially with industry-specific software installed on a server in the office. There is no API. There is a SQL Server database you “can get into if you know the password”, and a vendor sales rep offering an integration module at a price that fits no budget.
The honest answer here is rarely to hit the database directly, even when it is technically possible. Writing into tables you do not own, without knowing the product’s internal rules, is the fastest route to corrupted data and a voided support contract. What we build instead is an intermediate layer that reads safely, exposes a clean API, and writes only through the paths the vendor supports.
Volume grows and the API limits show up
Everything works in testing with fifty records. Then comes the initial migration with eighty thousand, or the month-end peak, and you discover the API on the other side allows two hundred calls per minute.
Integrations that ignore this at design time turn into a recurring incident. The ones that account for it use queues, batching and retries with progressive backoff, and absorb the peak without anyone noticing.
How we approach it
We decide data ownership first
Before architecture and before code, we build a deeply boring and absolutely essential table: field by field, which system owns it. The CRM owns commercial data, the ERP owns tax and invoicing data, the support tool owns the ticket history.
The business signs off on that table, not the engineering team. From there the rule is simple: a field has one owner and every other system reads it. When a field genuinely needs two owners, we document it explicitly and define the conflict-resolution policy before building anything.
We pick the pattern before the technology
Not every integration needs to be real time, and real time is expensive to build and to operate. We separate three cases:
- Event-driven, real time (webhooks). When the data has to be on the other side within seconds: an order that triggers picking, a lead that comes in and needs a call while it is warm.
- Asynchronous queue. When ordering matters and the destination may be down. This is the default pattern for most serious CRM-to-ERP integrations.
- Scheduled batch. When the data tolerates hours of delay: accounting consolidation, reporting, product catalogues. It is the cheapest to build and maintain, and a great many companies are paying for an event-driven architecture to solve a problem an overnight job would have handled.
Idempotency and retries by design
Every integration will fail at some point: the network, a deploy on the other side, a maintenance window. The question is not whether it fails, but what happens next.
We design each operation so that running it twice produces the same result as running it once. It sounds like a minor technical detail, and it is the difference between an integration that recovers on its own and one that creates duplicate orders and an angry phone call. Every message carries a unique identifier, the destination records what it already processed, and retries are safe by construction.
Observability: finding out before your customer does
Every integration we put into production ships with three things: a log of what synced and when, an alert when the queue grows beyond normal, and a screen where someone from the business — not from IT — can see whether today went fine.
That last part gets skipped almost every time, and it is the one that prevents the classic scenario: the integration has been down for nine days and nobody noticed, because the errors go into a log nobody reads.
When we do not build a custom API
We will tell you if this is your case, even when it means a smaller project. If the standard connector covers what you need and your edge case can be handled with an internal convention, use it. If the volume is twenty records a month, one person spending ten minutes a week is cheaper than any development. And if both systems are being replaced next year, integrating something about to die is burning money.
Custom development pays off when the logic that makes you different lives precisely in the gap no product covers, and when the cost of manual error — wrong invoices, drifting stock, duplicate customers — already exceeds the cost of building it properly.
Mistakes worth avoiding
- Starting with the technology instead of data ownership. Choosing the integration tool before deciding which system owns each field guarantees you will rebuild it.
- Syncing everything because you can. Every synced field is a field that can drift. Move only what someone will genuinely use on the other side.
- Testing only with clean data. Production data has phone numbers in odd formats, duplicate names, and records from 2014 with no tax ID. Test against a real copy before you go live.
- Not planning the initial load. Migrating history almost never works through the same mechanism as day-to-day operation, and finding that out on go-live Friday is expensive.
- Leaving the integration without an owner. When the project ends, somebody still has to watch the alerts. If that person does not exist, the integration degrades in silence.
- Writing straight into a third-party product’s database. It works right up until the vendor changes the schema, and then you have neither your data nor your support.
What to ask before you hire
If you are evaluating someone for this, four questions separate the people who have run integrations in production from the ones who have built a demo:
- What happens if the destination system is down for two hours? (If the answer does not mention queues or retries, keep looking.)
- How do you prevent duplicates if the same message arrives twice?
- Who owns each field, and how do we decide that?
- How do I — a non-technical person — find out that today’s sync failed?
Closing
At LMNHUB we build this kind of integration with Python, Django and FastAPI, and we have been doing it for years in settings where the data matters: connecting Zoho CRM to the ERP, to the phone system, or to external portals, and maintaining systems that have been in production for years. If you have two systems that ought to be talking and today the link between them is a person with a spreadsheet, tell us about your case and we will come back with a concrete approach — which pattern fits, what syncs and what does not — rather than a generic quote.