01 / 08 screenshot-2026-08-14-at-12.13.31-2.png
Screen 1 of 8 Screen 2 of 8 Screen 3 of 8 Screen 4 of 8 Screen 5 of 8 Screen 6 of 8 Screen 7 of 8 Screen 8 of 8

CRM for a Book E-commerce Project: Wildberries, Ozon and Yandex Market

 

For the second-hand online store, an internal CRM was developed, which leads each book from a stock photo to a card at once on three marketplaces — Ozon, Wildberries and Yandex Market — and removes it from the two remaining sites at the time of sale at any of them.

The task differed from conventional card automation in that each — product is a single copy: the same book cannot be sold twice. Three sites are polled independently, and the book is physically one, so the entire architecture is built around one — overselling should be excluded at the data level, not at the agreement level.

The challenge

An ordinary seller on the marketplace downloads the card once and trades with the remainder. Each — book has a unique copy with its own state, year of publication and notes on the margins: the card must be collected once, sold once and immediately removed from everywhere.

This led to three requirements for the system:

  • for each book to re-recognize the output, write a description, select a category and calculate the price for the current market
  • publish the same product on three sites at once, but instantly reset the balance on the other two when selling on any of them
  • eliminate the situation when the marketplace sells goods that are no longer — is a fine and a drop in the seller's rating.

How the conveyor belt works

Each book goes through seven stages, each of which — is a separate background problem with its own status, try counter, and heartbeat. The operator is not waiting anywhere for an external API response in the browser: uploaded a photo of — and moved to the next stack.

1. Reception.

The storekeeper has one screen — mass upload photos. He photographs the book, notes the main frame and up to two technical shots of the root, points to the shelf. The article is assigned automatically.

2. Vision recognition model

Photos go to OpenAI Responses API with a strict JSON schema: the model must return the structure, not free text. Each frame is given its role as — cover, root, spread with — copyright and its own instructions. Name, author, publisher, ISBN, year, series, type of binding, condition with a list of defects, annotation and hashtags are extracted. Vision detailing is forced into maximum mode: in simplified mode, the image is compressed to approximately 512 px, and the hard binding becomes indistinguishable from soft.

3. Market enrichment

According to the recognized data, the system searches for the same edition on Ozon and Wildberries, evaluates coincidences by the degree of similarity and picks up from analog cards what is not in the photo: number of pages, dimensions, weight, category Ozon with its mandatory characteristics.

4. Pricing

The pricing policy is implemented as a code: electronic versions and analogues with other binding are discarded, emissions are cut off by coefficients to the median, the average is taken at three or more prices found and the — median at a smaller number, premiums for the author's autograph are applied, the rarity of the publication and the lack of competition, the price does not fall below the specified threshold. If the price range is too large, the disputed sample is disassembled by a separate AI arbitrator.

5. Publication

For each site, a payload is assembled: Ozon — has category attributes and video covers, Wildberries — has a card with media and an article of the seller, Yandex Market — has its own offer format. The publication logs along with the statuses and the full body of the request and response, experiences the site's limitations on the frequency of requests with resending to its own delay header, and the final background problem checks that the card is indeed published, and warms up the remainder.

6. Sale and withdrawal from other windows

Orders are received by API survey of each site and webhooks. The order is decomposed into positions, — positions on movements in the warehouse, movements are recorded in a single inventory record of the book, the uniqueness of which is guaranteed by a limitation at the database level. After that, cross-market place withdrawal works: the remainder on the other two sites goes to zero. Withdrawal is allowed only on a confirmed order event — assumption type « card disappeared from issuance, probably sold » such right does not give.

7. Verification and observation

Every hour, a snapshot of the dissynchron is built: the local residue is checked with the remainder on each site, with an analysis of the reasons for the discrepancy and a mass correction button directly from the adminka. A separate team collects the state of the conveyor — hung recognition, stuck publications, fallen for the day background tasks. Additionally configured upload published books in Google Tables and backup database on FTP every two hours on weekdays, photos — once a day.

The four tasks for which everything was done.

Race for a single copy

Three sites are interviewed independently, there are several workshops, and the — book is one. The risk of selling the same product is closed twice by several layers of protection: a unique inventory entry to the book at the database schema level, a only-append movement log with a fixation of the source of change, versioning of residue unloadings, the uniqueness of background tasks within one site office and the prohibition to change other people's remains without a confirmed order.

Competitor prices without public API

Neither Ozon nor Wildberries search the catalog outwards, and without peer prices pricing is impossible. To do this, two separate Node services are written on the headless Chromium: they work in an internal docker network without ports open to the outside, go under a service token, first try out the JSON endpoint search and only then disassemble the HTML directory, intercepting the same answers that the site downloads itself. Cookies is read-only, and each request has a diagnosis and a hard general timeout.

The route to the site that does not exist

From the combat IP was not available part of the routes to marketplaces. The solution was sidecar-container with SOCKS5-proxy in the same internal docker-network, where the browser service addresses by the name of the container; the access key is stored by a separate read-only secret, the container itself is raised in read-only mode without increasing privileges. The proxy is switched on point-by-point through environment variables, with no changes to the code.

Everything falls, and it's normal.

External API respond with errors, give the balance with a delay and sometimes report the wrong status of the goods. Therefore, each heavy step of the — conveyor is a idempotent background task with a unique key, restart counter and heartbeat, and on top there is a set of repair Artisan commands: lift the hanging conveyor, fix falsely « sold » goods Ozon, restore the lost residue of Wildberries, reduce the catalog to reality, collapse duplicate cards.

Architecture

The core of — is the usual Laravel monolith: it is convenient, so long as all the complexity is in business logic, not in the load. Externally removed only that physically does not fit in the PHP-process — two browser services and proxy. Everything goes up one docker compose:

  • app — Laravel 13 on PHP-FPM, queue under supervisor, task scheduler
  • nginx — front, static, TLS via certbot
  • mysql — MySQL 8, 37 tables
  • wildberries_search — Node + Chromium: search for analogues on Wildberries, health-endpoint
  • ozon_search — Node + Chromium with a permanent browser profile: search for analogues on Ozon
  • outline_proxy — SOCKS5 is an output for the Ozon browser service.

Stack:

  • PHP 8.3, Laravel 13, Eloquent, database queues, Laravel Sanctum, Intervention Image for photo processing, PhpSpreadsheet for importing residues from unloadings
  • — Blade, Alpine.js, Tailwind 4, Vite, without SPA: warehouse interface, it is important speed and density, not a rich client state
  • integration — Seller API Ozon, Wildberries and Yandex Market, OpenAI Responses API, Google Sheets, FTP backups, notifications in messengers
  • three access roles: administrator, manager with stripped partitions and warehouse user, who has access to exactly one screen — upload photo.

Scale

This is not a showcase and not an online store — buyer does not come here at all. This is an internal tool of warehouse and content managers, which all the complexity is hidden in queues, integrations and reconciliations:

  • about 69ZZ ZZ000
  • 56 Service Classes
  • 28 background queues (queue-jobs)
  • 42 Artisan commands, mainly repair and diagnostic
  • 68 migrations, 37 tables
  • 63 file tests for business logic integrations
  • 76 Blade templates admin
  • 3 marketplaces, each has its own multi-cabinet.

The result

A book card on three sites is assembled from photos without manual input: a person spends time shooting and shelf, not on description, category and price. The price has ceased to be a — flavoring policy is described by the code, with analogue filters, cut-offs and premiums, and applies equally to any book.

Overselling is closed at the level of data schema, not agreements: one copy, movements on it are logged, and removal from other people's windows requires a confirmed order. The discrepancies are visible before they are noticed by the — marketplace thanks to the hourly snapshot of the dissynchron, the state of the conveyor and the log of each publication with the query and response body.

The system is recoverable: the business logic of integrations is covered with tests, and for cases where the external API behaves not according to documentation, there is a set of repair teams.