Two-Way Product Catalog Sync with MoySklad

Products on your site show incorrect stock levels, managers update prices in **MoySklad** but they don't reflect on the site. Orders have to be entered manually—leading to 20–30% shipping errors and lost customers. **Two-way synchronization** solves this: products, stock, orders, and statuses update

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1281
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    977
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1026
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1103
  • image_website-_0.webp
    Website development for Red Pear
    550

Products on your site show incorrect stock levels, managers update prices in MoySklad but they don't reflect on the site. Orders have to be entered manually—leading to 20–30% shipping errors and lost customers. Two-way synchronization solves this: products, stock, orders, and statuses update automatically. The result is accurate data on both the site and MoySklad without your manual effort. Savings on manual data entry can reach 200,000 rubles per year.

What problems does two-way synchronization solve?

Stock discrepancies are the most common pain point. A customer sees a product in stock, but it's already gone, or vice versa. Our synchronization updates stock every 5 minutes, reducing shipping errors by 80% compared to manual management. The second problem is manual order transfer. Employees spend up to 2 hours daily duplicating information. Two-way synchronization automatically creates an order in MoySklad when placed on the site and updates the status upon shipment. The third is data conflicts. If a price is changed simultaneously in both systems, we apply a clear source of truth rule: prices and stock from MoySklad, SEO and images from the site.

How we implement the integration

Architecture and stack

We use the MoySklad REST API version 1.2, PHP 8.3 with Laravel 11 framework and queues via Redis. Authentication uses a Bearer token. All requests go through an HTTP client with retry logic on errors. Webhooks are configured for order change events so statuses update instantly. The architecture accounts for N+1 queries and uses chunked data loading (batches of 100 items).

According to the official MoySklad API documentation, this ensures reliable integration.

Example API client implementation
class MoiSkladClient { private string $baseUrl = 'https://api.moysklad.ru/api/remap/1.2'; private function headers(): array { return [ 'Authorization' => 'Bearer ' . config('services.moysklad.token'), 'Content-Type' => 'application/json;charset=utf-8', 'Accept-Encoding' => 'gzip', ]; } public function get(string $path, array $params = []): array { return Http::withHeaders($this->headers()) ->get("{$this->baseUrl}/{$path}", $params) ->throw() ->json(); } public function post(string $path, array $data): array { return Http::withHeaders($this->headers()) ->post("{$this->baseUrl}/{$path}", $data) ->throw() ->json(); } } 
Entity Source of truth Notes
Products (name, description, price) MoySklad Managers edit there
Stock MoySklad Updates on receipt/sale
Product images Site Uploaded via CMS
SEO fields (meta, slug) Site Do not exist in MS
Orders Site → MS Created on site, sent to MS
Order statuses MS → Site Manager changes in MS

Fetching products and stock

class ProductSyncService { public function fetchProducts(int $offset = 0, int $limit = 100): array { return $this->ms->get('entity/product', [ 'offset' => $offset, 'limit' => $limit, 'expand' => 'productFolder,images', 'filter' => 'archived=false', ]); } public function fetchStocks(): array { return $this->ms->get('report/stock/all/current', [ 'stockType' => 'stock', 'includeRelated' => false, ]); } public function syncToSite(): void { $offset = 0; $limit = 100; do { $response = $this->fetchProducts($offset, $limit); $products = $response['rows']; foreach ($products as $msProduct) { $this->upsertProduct($msProduct); } $offset += $limit; } while ($offset < $response['meta']['size']); $stocks = $this->fetchStocks(); foreach ($stocks as $stock) { Product::whereExternalId($stock['assortmentId']) ->update(['stock' => max(0, (int)$stock['stock'])]); } } private function upsertProduct(array $msProduct): void { $msId = $msProduct['id']; $data = [ 'external_id' => $msId, 'name' => $msProduct['name'], 'article' => $msProduct['article'] ?? null, 'price' => $this->parsePrice($msProduct['salePrices'][0]['value'] ?? 0), 'description' => $msProduct['description'] ?? '', 'ms_updated_at'=> Carbon::parse($msProduct['updated']), ]; $product = Product::updateOrCreate(['external_id' => $msId], $data); } private function parsePrice(int $msPrice): float { return $msPrice / 100; } } 

Order transfer and webhooks

public function pushOrder(Order $order): string { $positions = []; foreach ($order->items as $item) { $positions[] = [ 'assortment' => [ 'meta' => [ 'href' => "{$this->baseUrl}/entity/product/{$item->product->external_id}", 'type' => 'product', ], ], 'quantity' => $item->quantity, 'price' => $item->price * 100, ]; } $msOrder = $this->ms->post('entity/customerorder', [ 'name' => "Order #{$order->id}", 'organization' => [ 'meta' => [ 'href' => "{$this->baseUrl}/entity/organization/" . config('services.moysklad.org_id'), 'type' => 'organization', ], ], 'agent' => $this->getOrCreateCounterparty($order->customer), 'positions' => $positions, 'description' => "Source: site\nEmail: {$order->customer->email}", 'attributes' => [ [ 'meta' => ['href' => $this->orderIdAttributeHref()], 'value' => (string)$order->id, ], ], ]); $order->update(['ms_order_id' => $msOrder['id']]); return $msOrder['id']; } 

When an order changes in MoySklad, the webhook notifies our server and the status is automatically updated on the site:

public function handleMsWebhook(Request $request): Response { $events = $request->json('events', []); foreach ($events as $event) { if ($event['meta']['type'] === 'customerorder') { $msOrderId = basename($event['meta']['href']); SyncOrderStatusJob::dispatch($msOrderId); } } return response()->noContent(); } 
class SyncOrderStatusJob implements ShouldQueue { public function handle(MoiSkladClient $ms): void { $msOrder = $ms->get("entity/customerorder/{$this->msOrderId}"); // status mapping and order update } } 

How often is data updated and how are conflicts resolved?

Stock synchronizes every 5 minutes, products and orders every 10 minutes. This keeps data up-to-date without excessive API load. The frequency can be adjusted to your needs.

Conflicts are inevitable with two-way synchronization. Our approach: price, stock, SKU — MoySklad priority; SEO fields and images — site; title — by last modification date. We ensure important changes are never lost. Shipping errors cost on average 50,000 rubles per month — synchronization minimizes them.

Comparison of approaches

Characteristic One-way Two-way
Products, prices, stock
Orders (site→MS)
Statuses (MS→site)
Conflict resolution not needed
Manager manual work ~1 hour/day automated
Shipping errors 20-30% <5%

Two-way synchronization reduces order processing time by 10x and virtually eliminates shipping errors.

Why choose two-way synchronization?

We have been integrating online stores with MoySklad for over 5 years, completing 30+ projects including complex ones with thousands of SKUs. Our engineers have proven expertise in Laravel and REST APIs. We guarantee the integration will work and promptly fix any issues. Savings on manual data entry reach 200,000 rubles per year, and costs for fixing shipping errors drop to 50,000 rubles per month.

Process and timeline

  1. Analysis — we study the catalog structure, define entities and mapping rules.
  2. Design — we design the integration: which fields, frequency, error handling.
  3. Implementation — we write code, configure webhooks, queues.
  4. Testing — we run a test sync on a data copy and compare results.
  5. Deployment — we deploy to production and monitor.

Estimated timeline: one-way sync (MoySklad → site) — 2–3 business days; two-way (with orders, statuses, webhooks) — 6–8 business days. The timeline may increase for complex catalog structures (variants, bundles). We offer a free audit of your current system and propose the optimal solution. Contact us to permanently eliminate manual data transfer. Get a consultation today.