Change feed
Everything that changes in the WMS — a product edited, goods received, an order shipped — is written to the change feed. Your system reads the feed regularly, applies the changes and confirms how far it has read. There are no pushes from the WMS: your system asks.
Reading the feed needs the Change feed read capability. Which data types appear in the feed is listed in Integration data types. The feed carries only changes made in the WMS: what your own system imported does not come back to it.
The reading loop
-
Get your checkpoint — the last event you confirmed:
GET /api/v1/integrations/checkpoints/ERP_MAIN{"id": 1, "externalSystemCode": "ERP_MAIN", "lastAcknowledgedEventId": 4211}On the very first run
lastAcknowledgedEventIdis missing: read from the beginning, or start from a resync. -
Read a page of events after that event:
GET /api/v1/integrations/change-feed?externalSystemCode=ERP_MAIN&limit=100&fromEventId=4211limit— required, from 1 to 500.fromEventId— the events after this one are returned; leave it out to start from the beginning.entityTypes— optional; repeat it to read only some data types:&entityTypes=RECEIPT&entityTypes=OUTBOUND_ORDER. Do not confirm events read this way — see the rules below.
{"externalSystemCode": "ERP_MAIN","events": [{"event": {"eventId": 4212,"entityType": "RECEIPT","internalEntityId": "01a17a6f-60a6-77be-b765-b7e9341d7efe","scopeType": "WAREHOUSE","warehouseId": "01a16373-d6be-7746-8eab-fc8e447bfea8","entityVersion": 3,"occurredAt": "2026-10-27T10:02:11.418Z"},"payload": {"id": {"int": "01a17a6f-60a6-77be-b765-b7e9341d7efe", "ext": "rcpt-2026-0105"},"status": "RECEIVED"}}]}Events come in ascending
eventIdorder.event.internalEntityIdis the WMS identifier of the record, the same value aspayload.id.int.payloadis the current state of the record at the moment you read it, with identifiers as{int, ext}pairs (see Building the payload). If a record changed several times, each event brings its latest state. -
Apply the events in your system.
-
Confirm the last event of the page:
POST /api/v1/integrations/checkpoints/acknowledge{"checkpointId": 1, "eventId": 4212} -
Repeat from step 2 with
fromEventIdset to that event, until a page comes back empty. Then wait — a minute is usual — and start again.
Rules to rely on
- An event can arrive twice. If your system applies a page and stops before confirming it, the page comes again on the next run. Make applying an event safe to repeat: look the record up by its identifier and overwrite it, never add to it blindly.
- There is one checkpoint for all data types. Read the feed without
entityTypesand skip the types you do not need in your own code. If you confirm the last event of a page filtered by type, the events of the other types before it are skipped for good. - The checkpoint only moves forward. Confirming the event that is already confirmed is accepted and changes nothing; confirming an older event is rejected with 400.
- Do not skip a failed event silently. Either stop and retry the page later, or put the event into your own retry queue before you confirm the page.
- If the WMS asks for a full resync of your system, reading the feed returns 400 until you complete a resync.
Code
The first function runs the loop above until the feed is empty. The second moves the checkpoint to the event a resync returned, so that reading continues right after the snapshot. Both use the client from API basics.
- JavaScript
- Python
- 1C
// Reads the change feed from the last confirmed event to the end, hands every
// event to `handleEvent` and confirms each page. Run it on a schedule, e.g. every minute.
// The same event can arrive more than once, so `handleEvent` must be safe to repeat.
// Your system has one checkpoint for all data types: read every type here and
// skip the ones you do not need inside `handleEvent`.
async function pollChangeFeed(handleEvent, {limit = 100} = {}) {
const code = config.externalSystem;
const {body: checkpoint} = await wmsRequest('GET', `/api/v1/integrations/checkpoints/${code}`);
let lastEventId = checkpoint.lastAcknowledgedEventId ?? null;
let handled = 0;
for (;;) {
const query = {externalSystemCode: code, limit};
if (lastEventId !== null) query.fromEventId = lastEventId; // events after this one
const {body} = await wmsRequest('GET', '/api/v1/integrations/change-feed', {query});
if (body.events.length === 0) return handled;
for (const {event, payload} of body.events) {
await handleEvent(event, payload); // throw to stop: the page stays unconfirmed
handled += 1;
}
lastEventId = body.events.at(-1).event.eventId;
await wmsRequest('POST', '/api/v1/integrations/checkpoints/acknowledge', {
body: {checkpointId: checkpoint.id, eventId: lastEventId},
});
}
}
// After a resync: moves the checkpoint to the snapshot's event, so the next
// pollChangeFeed starts right after the snapshot. Never moves it back.
async function startChangeFeedAt(eventId) {
const code = config.externalSystem;
const {body: checkpoint} = await wmsRequest('GET', `/api/v1/integrations/checkpoints/${code}`);
const current = checkpoint.lastAcknowledgedEventId ?? null;
if (current !== null && Number(current) >= Number(eventId)) return;
await wmsRequest('POST', '/api/v1/integrations/checkpoints/acknowledge', {
body: {checkpointId: checkpoint.id, eventId},
});
}
def poll_change_feed(handle_event, *, limit=100):
"""Reads the change feed from the last confirmed event to the end, hands every
event to `handle_event(event, payload)` and confirms each page. Run it on a
schedule, e.g. every minute. The same event can arrive more than once, so
`handle_event` must be safe to repeat. Your system has one checkpoint for all
data types: read every type here and skip the ones you do not need inside
`handle_event`."""
code = CONFIG["external_system"]
checkpoint = wms_request("GET", f"/api/v1/integrations/checkpoints/{code}").json()
last_event_id = checkpoint.get("lastAcknowledgedEventId")
handled = 0
while True:
query = {"externalSystemCode": code, "limit": limit}
if last_event_id is not None:
query["fromEventId"] = last_event_id # events after this one
events = wms_request("GET", "/api/v1/integrations/change-feed", query=query).json()["events"]
if not events:
return handled
for item in events:
handle_event(item["event"], item["payload"]) # raise to stop: the page stays unconfirmed
handled += 1
last_event_id = events[-1]["event"]["eventId"]
wms_request("POST", "/api/v1/integrations/checkpoints/acknowledge",
body={"checkpointId": checkpoint["id"], "eventId": last_event_id})
def start_change_feed_at(event_id):
"""After a resync: moves the checkpoint to the snapshot's event, so the next
poll_change_feed starts right after the snapshot. Never moves it back."""
code = CONFIG["external_system"]
checkpoint = wms_request("GET", f"/api/v1/integrations/checkpoints/{code}").json()
current = checkpoint.get("lastAcknowledgedEventId")
if current is not None and int(current) >= int(event_id):
return
wms_request("POST", "/api/v1/integrations/checkpoints/acknowledge",
body={"checkpointId": checkpoint["id"], "eventId": event_id})
// Reads the change feed from the last confirmed event to the end, passes every
// event to ОбработатьСобытие and confirms each page. Run it from a scheduled job.
// The same event can arrive more than once, so ОбработатьСобытие must be safe to repeat.
// Your system has one checkpoint for all data types: read every type here and
// skip the ones you do not need inside ОбработатьСобытие.
Функция ОбработатьЛентуИзменений(Подключение, Лимит = 100) Экспорт
Код = Подключение.КодВнешнейСистемы;
КонтрольнаяТочка = ВыполнитьЗапросWMS(Подключение, "GET", "/api/v1/integrations/checkpoints/" + Код).Тело;
ПоследнееСобытие = КонтрольнаяТочка["lastAcknowledgedEventId"]; // Неопределено on the first run
Обработано = 0;
Пока Истина Цикл
Параметры = Новый Соответствие;
Параметры.Вставить("externalSystemCode", Код);
Параметры.Вставить("limit", Лимит);
Если ПоследнееСобытие <> Неопределено Тогда
Параметры.Вставить("fromEventId", ПоследнееСобытие); // events after this one
КонецЕсли;
События = ВыполнитьЗапросWMS(Подключение, "GET", "/api/v1/integrations/change-feed", Параметры).Тело["events"];
Если События.Количество() = 0 Тогда
Возврат Обработано;
КонецЕсли;
Для Каждого Элемент Из События Цикл
// An exception stops the run: the page stays unconfirmed and is read again next time.
ОбработатьСобытие(Элемент["event"], Элемент["payload"]);
Обработано = Обработано + 1;
КонецЦикла;
ПоследнееСобытие = События[События.ВГраница()]["event"]["eventId"];
Подтверждение = Новый Структура("checkpointId, eventId", КонтрольнаяТочка["id"], ПоследнееСобытие);
ВыполнитьЗапросWMS(Подключение, "POST", "/api/v1/integrations/checkpoints/acknowledge", , Подтверждение);
КонецЦикла;
КонецФункции
// After a resync: moves the checkpoint to the snapshot's event, so the next
// ОбработатьЛентуИзменений starts right after the snapshot. Never moves it back.
Процедура НачатьЛентуИзмененийС(Подключение, ИдСобытия) Экспорт
Код = Подключение.КодВнешнейСистемы;
КонтрольнаяТочка = ВыполнитьЗапросWMS(Подключение, "GET", "/api/v1/integrations/checkpoints/" + Код).Тело;
Текущее = КонтрольнаяТочка["lastAcknowledgedEventId"];
Если Текущее <> Неопределено И Текущее >= ИдСобытия Тогда
Возврат;
КонецЕсли;
Подтверждение = Новый Структура("checkpointId, eventId", КонтрольнаяТочка["id"], ИдСобытия);
ВыполнитьЗапросWMS(Подключение, "POST", "/api/v1/integrations/checkpoints/acknowledge", , Подтверждение);
КонецПроцедуры
Процедура ОбработатьСобытие(Событие, Данные)
// Your code: Событие["entityType"] says what changed, Данные is its current state.
// Skip the types you do not need; find your record by Данные["id"]["ext"] (your identifier).
КонецПроцедуры