Rozetka Seller API: pulling orders when there are no webhooks
Rozetka sends no webhooks and filters orders by date only. Here is what a reliable import loop looks like because of that — and the three things that quietly break a naive one.
Most integrations start by asking where Rozetka will send its webhook. The answer is nowhere. Rozetka's Seller API is a request API, not an event API: until you ask whether there are new orders, you will not hear about them.
That changes the whole architecture. Instead of an endpoint that waits, you need a loop that runs on a schedule, remembers where it stopped last time, and creates no duplicates when the same run happens twice. Below is the contract as it actually behaves, and the three places where a naive implementation silently loses orders.
The contract: what to know before the first request
The values below are taken from the official Seller API documentation and checked against a real cabinet.
- Base
- api-seller.rozetka.com.ua — a separate host from the public site
- Authentication
- POST /sites with the seller login and the password base64-encoded. The response carries a token that lives about 24 hours.
- Reading orders
- GET /orders/search — always with line items, delivery and buyer expanded, otherwise the order arrives with neither products nor an address
- Time filter
- By DATE, not by time. The contract offers no "since this minute" precision.
- Line items
- An array of purchases inside the order; a row flagged as deleted is present in the response and must be skipped
- Statuses
- Numeric, and dependent on the individual cabinet's configuration — not a universal list
- Writing status
- PUT /orders/{id} — after login, with the same token
- Products
- GET /items/search to read; PUT /items/update-price-stock/{id} for price and stock. Creating a listing over the API is not possible.
- Currency
- Hryvnia
The import loop
- 1
Login and token
Exchange login and password for a token. Cache it: logging in on every request adds latency and needless noise in the marketplace's own audit trail.
- 2
Read the cursor
Fetch the stored date of the last successful run. On the very first run take one day back, not "all time": importing the entire history on run one is the fastest way to a timeout and an opaque partial state.
- 3
Query with an overlap
Ask for orders changed since the cursor date MINUS one day. The overlap is not paranoia — it follows directly from the date-only filter (detailed below).
- 4
Normalise
Map the response into your own order model with a pure function and no side effects: it can then be tested against a saved payload with no network call at all.
- 5
Idempotent write
Create the order under a key built from the Rozetka order id. A repeated run creates no second order — it finds the existing one.
- 6
Enrichment
On every pass, not only at creation, refresh the status, payment, delivery method and waybill number as soon as Rozetka returns them.
- 7
Write the cursor
And only after a successful pass move the cursor. A cursor advanced before processing loses exactly the orders the run failed on.
Why a daily cursor needs an overlap
The "changed since date X" filter has no time component. That means you cannot ask what changed in the last 15 minutes — only what changed today.
Hence the midnight-boundary problem. A run at 23:58 sees today's orders and writes the cursor as "today". A run at 00:03 asks for orders changed since "today" — but today is now a different date, and an order created at 23:59 lands in neither query. It simply disappears, and the worst part is that nothing fails: the logs are clean, the counters are green, and the order is not there.
The cure is cheap: always query from the cursor date minus one day. You re-read up to a day of already-known orders every time — which is exactly why the previous step, idempotency, is mandatory rather than nice to have. Without it the overlap would manufacture duplicates daily; with it the overlap is free.
Three things that break a naive implementation
- A line item flagged as deleted arrives in the response alongside the live ones. Skip it or you get an order containing a product the buyer never bought, and a total that does not match the cabinet.
- Numeric statuses are not universal: the same numbers can mean different states in different cabinets. So the status mapping belongs in ONE explicit block you can see and verify, not as constants scattered through the code. And a transition you are unsure about is safer left unsent than guessed.
- Moving into delivery requires a waybill. Setting "shipped" without a waybill number is not a state Rozetka will accept the order into, so that transition should not be part of an automatic reverse sync.
Prices and stock: only what is already published
Updating price and quantity works by SKU: Rozetka's listing article field is matched against the product SKU in your system. That implies two things. First, SKU discipline matters more than any code: a product with no SKU, or one that differs by case or a stray space, will not match. Second, the API updates existing listings only; it cannot create a product, so publishing a new position stays an action in the seller cabinet.
Quantity should only be sent for products where you genuinely track stock. A made-to-order product should not receive a quantity of zero merely because its stock field is empty — that pulls it out of sale for no reason at all.
Every write into someone else's account is off by default
A seller arrives with products that already live on the marketplace. So reading can be switched on immediately, while any write — prices, stock, statuses — waits for a dry run that shows exactly what would change while changing nothing. This is not paranoia: one mistaken bulk price push across a live catalog costs more than the whole integration.
Frequently asked
Does Rozetka have order webhooks?
No. The Seller API answers requests but emits no events, so new orders can only be picked up by periodic polling. A practical interval is 15 minutes: more often buys nothing given the date-only filter, less often is noticeable to a manager.
How long does a Seller API token live?
About a day. Log in lazily — when there is no token or it stops being accepted — rather than on every call.
Why is the password base64-encoded?
That is what the login contract specifies. It is encoding, not encryption: the password must still be stored encrypted on your side, and the exchange must happen over HTTPS.
Can a product be created on Rozetka over the API?
No. The API can read existing listings and update their price and stock. Creating a new position happens in the seller cabinet.
How do I avoid duplicate orders on a repeated run?
With an idempotency key built from the Rozetka order id, enforced at the database write. It is a precondition, not an optimisation: the cursor overlap re-reads known orders every single day.
Do I need a separate worker?
In Obriym CRM, no: polling is built in and runs on a schedule on the CRM side, so the seller only has to save their cabinet login. If you are building the integration yourself, you do need a worker — and the loop above is written for it.
Would rather not build this yourself?
In Obriym CRM the polling loop, idempotency, status enrichment and reverse sync already exist — you only save your Rozetka cabinet login.
Try it free