Manual exports gone: stock in the accounting system, on the website and on the marketplaces now agrees within minutes
We built HTTP services on the accounting side and an integration layer in Python exchanging over RabbitMQ. Two-way synchronisation of products, stock, prices and orders between the accounting system, the website and the marketplaces — without exposing the accounting system to the internet.
This is a representative build based on our expertise and stack. The architecture and the approaches are real. The metrics and the context are given as a reference point for projects of similar complexity, not as a delivered result.
About the client
A wholesale distributor of electrical goods. The inventory system is the trade edition of their ERP, deployed on-premise with no external IP. Sales run through three channels: a website, wholesale reps, and marketplaces. The catalogue holds 8,000 SKUs with prices and stock changing often.
This is the typical configuration for a mid-sized distributor: the accounting system is the source of truth for stock and prices, and around it the sales channels have grown up, learning that truth late.
The problem
How the exchange worked
The link between the accounting system and the channels rested on manual exports:
- Stock and prices to the website — a spreadsheet export once a night, by a script written by a contractor who had left. The price file format occasionally drifted, and the website silently uploaded zeros.
- Stock to the marketplaces — a manager exported from the accounting system and uploaded into the marketplace back offices “when they got round to it”, usually every day or two.
- Marketplace orders — an operator copied them into the accounting system from the back offices by hand, sixty to a hundred a day.
In money
A day’s delay meant the website and the marketplaces were selling stock that no longer existed. Every week produced dozens of oversells — orders for goods that were not there. On the marketplaces that means cancellation penalties and a falling seller rating. By the commercial director’s estimate, direct losses on penalties and cancellations ran into six figures a month in roubles, plus one and a half full-time operators doing nothing but moving data.
Why not an off-the-shelf connector
The standard website-to-accounting module covered only the website and could not handle their specifics: the client had their own rules for tiered wholesale pricing and complex reservation logic. No connector existed for their accounting version with the item-code mapping they needed. It became clear that what was required was an exchange built around their process, not a universal box.
The approach
Discovery: where the truth actually lives
The first week went into auditing the exchanges and the data model. The key finding: the website, the marketplaces and the accounting system do not share item codes — each platform has its own SKU, and the mapping lived in one manager’s head. Without a mapping table any automated synchronisation would have produced garbage.
The second finding: the accounting system retroactively corrects receipt documents, so a stock snapshot taken at export time went stale quickly. The exchange had to survive history being recalculated.
Key architectural decisions
HTTP services on the accounting side, not files. We built a set of HTTP services for reading stock and prices and for accepting orders. OData was used for the initial export of reference data, but all business logic — tiered pricing, reservations — went into the HTTP services.
Exchange over RabbitMQ, because the accounting system is closed. It has no external IP and should not have one. We put a message bus in place: the accounting system publishes stock changes to a queue from inside the perimeter, and the integration layer outside reads them. There is no direct access from the internet, only a VPN for maintenance.
The SKU mapping table as a first-class entity. A single mapping table across all systems, with an admin panel where a manager maps new items. That table became the core of the whole synchronisation.
Staging of raw data. Every response from the accounting system and from the platforms is stored as it arrived, and the views are recomputed. When a document is corrected retroactively, the exchange survives it without manual intervention.
The solution in detail
Stock and price synchronisation
The Python integration layer listens to the change queue. When stock or price changes on an item it:
- resolves the matching records through the mapping table;
- recalculates the wholesale price by tier and subtracts reservations;
- pushes the figure to the website over REST and to the marketplace back offices over their APIs.
The average delay from a change in the accounting system to the storefront being current is about four minutes instead of a day. On the marketplaces, updates respect their rate limits — changes are batched so the integration does not hit throttling.
Accepting marketplace orders
Orders are pulled over the APIs, mapped onto accounting items and created as sales documents through the HTTP service. The operator no longer transfers orders by hand; they only handle exceptions — an item with no mapping — which after the first month came down to a handful a week.
Observability
Every exchange is logged, with alerts into a team channel: if the queue stalls, a platform returns an error, or the number of unmapped items grows, the team hears it from monitoring rather than from a marketplace. Metrics — exchange delay, error count, share of desynchronisation — go to Grafana.
The hardest parts
Marketplace rate limits
The problem. On a mass price change — a new price list across two thousand items — sending each change directly hit the platform limits, some requests were rejected, and stock figures diverged.
The fix. A buffer: changes accumulate for a few minutes and go out in batches within the platform’s limits. Critical events, such as an item dropping to zero, jump the queue, so nothing is sold that does not exist.
Divergence from retroactive corrections
The problem. The accounting system recalculated stock after receipts were posted retroactively, and the snapshot already sent to the storefront went stale before the next cycle.
The fix. We dropped snapshots in favour of an event model: the accounting system publishes the fact of a stock change. Plus a full daily reconciliation between the accounting system and each platform, with a discrepancy report. In the first month the reconciliation cleared accumulated desynchronisation across several hundred items.
Results
Sixty days after launch
- Stock currency went from a day to about four minutes. The website and the marketplaces show what is actually in the warehouse.
- Oversells and cancellation penalties down 94%. The remaining rare cases come from simultaneous orders in different channels, and the daily reconciliation closes them.
- Manual exports gone entirely. One and a half operator positions moved from moving data to working with customers.
- Marketplace orders post automatically, with the operator handling only exceptions.
The client received exchange documentation and training for their in-house team; they run support themselves, and we stayed available for further development.
What changes for each role
Integration is the most invisible kind of project: while it works, nobody notices it. Here is what changes for the people who live with it.
The online store operator. The nightly export that occasionally drifted and left the site selling zeros disappears entirely: stock and prices update every few minutes. The team learns about an exchange failure from an alert rather than from a customer — which is a fundamental difference in who discovers the problem first.
The marketplace manager. A mapping table across every system, with an admin panel, solves the main operational pain: previously the SKU mapping lived in one person’s head and would have left with them. Orders from the platforms post automatically, and the operator handles only exceptions.
The IT director. The key requirement usually sounds like “do not expose the accounting system to the internet”, and it is achievable: the system publishes changes from inside the perimeter over RabbitMQ. On-premise security is preserved and there are no inbound connections.
The lead accounting specialist. The HTTP services are written around this company’s specific rules for wholesale pricing and reservations, and the documentation and training transfer to the in-house team. The goal is for the exchange to be supported internally, without permanent dependence on a contractor.
The finance director. The effect is best measured in direct losses: cancellation penalties and oversells on the platforms. That is the one line where the result shows in the first month and needs no complicated attribution.
Stack
- Backend and integration: Python 3.12, FastAPI, RabbitMQ, PostgreSQL, structlog
- Accounting: HTTP services, OData, scheduled jobs
- Channels: website REST API, marketplace APIs
- Infrastructure: on-premise accounting with VPN, Docker Compose, Grafana with channel alerts
When this approach fits
It fits if:
- the accounting system is the source of truth and the link to the website, CRM or marketplaces rests on manual exports;
- oversells and penalties are happening because stock figures on the platforms are stale;
- the accounting system runs on-premise with no external IP and must not be exposed;
- item codes do not match between systems and the mapping lives in someone’s head.
It does not fit if:
- a standard off-the-shelf connector covers what you need without customisation;
- the catalogue is small and changes rarely — a scheduled export is enough.
A similar problem in your business?
In one call we will work out what this would be worth to you and which architecture fits.