Saudi commerce · Inventory architecture

Salla and ERP inventory sync: make every stock movement auditable

A practical architecture for syncing ERP stock with Salla using bulk deltas, branch mapping, change reasons, a durable outbox and scheduled reconciliation.

TOPIC HUBE-commerce Engineering
Original conceptual illustration of warehouses and a storefront connected through a durable inventory ledger; not Salla's internal infrastructure.
An editorial interpretation of the topic, followed by a practical execution diagram.

Inventory synchronization fails in a deceptively simple place: two systems both appear to hold a quantity, but they do not share the same history. An ERP may record receiving, damage, transfers and physical counts while the storefront subtracts stock as orders are created. If an integration repeatedly overwrites one number with another, a delayed job or retry can erase a valid movement.

Salla's current Merchant API offers a better set of primitives: bulk quantity updates with `increment`, `decrement` and `overwrite` modes, a `branch` and `reason_id`; quantity reads; a list of change reasons; and a quantity audit endpoint. These endpoints do not create a complete ERP connector by themselves. They let you build one whose movements can be explained, reconciled and repaired.

Choose the source of truth before writing code

A synchronization design needs one owner for each decision. The ERP or WMS can own physical available stock, Salla can own reservations and selling behavior, and the integration can own delivery state. Do not let a scheduled import, order webhook and warehouse adjustment all overwrite the same field independently.

Define the stock identity as at least `store + branch + product or variant`. Keep a mapping table from the ERP's warehouse and SKU identifiers to Salla's branch and product or variant IDs. Salla's List Branches endpoint exposes branch IDs and metadata; store the stable ID, not only a display name that an operator may rename. Reject unmapped or ambiguous identities into an exception queue rather than writing to the default branch silently.

Also define the quantity contract. Is the ERP publishing on-hand, available-to-sell, or a movement delta? Does it subtract safety stock? Are Salla orders already reflected in the ERP before the next sync? A correct API call with an unclear business quantity still produces incorrect inventory.

Prefer movements over blind snapshots

The bulk endpoint documents three modes. `increment` adds units, `decrement` removes units and `overwrite` replaces the quantity. Salla describes overwrite as suitable for initial setup or complete reset and advises caution for external synchronization. Increment and decrement preserve intent: receiving 12 units and writing off two damaged units remain two explainable movements instead of an unexplained change from 40 to 50.

A movement record in your integration can look like this:

movement_id: erp-warehouse-7-00018492
identity: store-44 / branch-349994915 / variant-8439405958
delta: -2
reason: damaged
occurred_at: 2026-09-26T11:41:08Z
state: pending

Persist that record before calling Salla. This is a transactional outbox pattern: the business transaction and its outbound intent are committed together, then a worker delivers the movement. A unique constraint on `movement_id` prevents the ERP from creating the same local movement twice. Serialize workers by inventory identity so two changes for the same variant and branch do not race.

Fetch the platform's reason list and map your ERP reason codes to returned IDs. The documentation currently shows reasons such as correction, restocking, receiving, damaged and lost or stolen, but example IDs are not configuration. Cache the resolved mapping with a refresh policy and stop a movement when its reason cannot be mapped. Preserving the reason makes operational investigation far easier than recording every change as a generic correction.

Build the bulk request deliberately

The write endpoint accepts product or variant identifiers, quantity, mode, branch and reason. Its published request schema spells the two identifier fields as `identifer_type` and `identifer`; clients must follow the actual API contract rather than silently correcting the spelling in serialized JSON. An illustrative payload is:

{
  "products": [
    {
      "identifer_type": "variant_id",
      "identifer": "8439405958",
      "quantity": 2,
      "mode": "decrement",
      "branch": "349994915",
      "reason_id": 525144736
    }
  ]
}

Use the example only as shape: obtain the real store, branch, variant and reason values for the merchant. The endpoint requires `products.read_write`; quantity, audit and reason reads use `products.read`, while discovering branches uses `branches.read`. Request only the scopes your application needs.

Keep batches bounded by count and by one operational context. Do not mix thousands of unrelated stores or a whole day's corrections into a single opaque request. Record a hash of the outbound payload, attempt number, response status and timestamps without logging access tokens or customer data.

Treat each stock movement as a durable business event, submit a controlled delta, then verify quantities and the audit trail instead of trusting transport success alone.
Treat each stock movement as a durable business event, submit a controlled delta, then verify quantities and the audit trail instead of trusting transport success alone. Open for a larger view

A 201 response is not final inventory state

The bulk endpoint's documented success response is HTTP 201 with a message saying the details were queued and may take several minutes. That means transport acceptance is not evidence that every quantity is already visible. Model delivery with states such as `pending`, `accepted`, `verified`, `failed` and `unknown`, rather than marking a movement complete at the first 201.

This distinction matters during timeouts. The public endpoint documentation does not describe a client idempotency key for a bulk movement. Replaying an `increment` or `decrement` after an ambiguous timeout can apply the delta twice if the first request was accepted. Application-level deduplication prevents duplicate sends from known local events, but it cannot prove what a remote server did after the connection broke.

For an unknown outcome, pause that inventory identity, read the current quantity and inspect the audit trail before deciding whether to retry. If evidence remains ambiguous, send the movement to human review or perform a controlled reconciliation. Blind retries are safe only for operations whose remote contract is explicitly idempotent; do not assume that property here.

Reconcile quantities and history

Run a scheduled reconciliation independently of real-time delivery. Read product quantities for each mapped branch and compare them with the integration's expected state. Classify differences: pending accepted writes, unmapped SKUs, manual merchant edits, ERP-only movements, order timing, or a genuinely failed update.

The audit endpoint adds context by returning old and new quantities, variant, reason, date and the acting user, and it supports filters including branch and keyword. It is valuable evidence, but the documented response does not expose your ERP `movement_id`. Matching by identity, delta, reason and time window is therefore correlation, not guaranteed end-to-end identity. Keep your own ledger as the authoritative delivery history and use Salla's audit as external evidence.

Do not repair every difference with overwrite. First calculate whether the discrepancy is understood. A manual correction made inside the merchant dashboard may be legitimate and should be imported back to the ERP or approved, not erased. Reserve overwrite for onboarding, an approved physical count or a controlled reset with a before-state snapshot and operator approval.

Roll out without risking all stock

Start with one store, one branch and a small group of low-risk SKUs. Capture an opening snapshot, process a known receipt, damage adjustment and return, then verify both quantity and audit output. Test an API timeout after submission, an unmapped variant, a renamed SKU and two simultaneous movements for the same branch.

Monitor outbox age, accepted-but-unverified count, reconciliation drift, mappings that fail, unknown outcomes, API validation errors and time from ERP commit to verified storefront quantity. Alerts should point to a movement and inventory identity, not merely say that “sync failed.”

The goal is not instant copying. It is controlled convergence with an explanation for every unit. Salla supplies bulk deltas, branch context, reasons, current quantities and an audit view. A reliable integration adds durable movement identities, ordered delivery, cautious retry rules, reconciliation and an approval path for discrepancies. That combination lets a merchant scale operations without turning stock corrections into guesswork.

Official references

These references document the tools discussed. Examples and design decisions are illustrative and should be adapted to the project and its versions.

Prepared by: Noor Yasser

FROM DECISION TO DELIVERY

Working through a similar engineering challenge?

I help teams turn architecture decisions into a clear scope and dependable, reviewable implementation.

Book a 30-minute callRelated serviceSalla & Zid apps and merchant toolsRelevant projectZeedly