ПерфектПак · API v1.1
openapi.yaml ↗

Статусы заказов FBS в CRM

Зачем

CRM или таск-трекер поддержки обычно должен знать статус заказа не хуже, чем ваш личный кабинет — чтобы оператор поддержки видел «собирается» или «доставлен» без переключения в другую систему. Опрашивать GET /v1/orders целиком ради этого дорого (у крупного селлера — до ~900 заказов в сутки); вебхук order.status_changed присылает только то, что реально изменилось.

Подписаться на order.status_changed

POST https://api.perfektpak.ru/v1/webhooks
Authorization: Bearer $PP_KEY
Content-Type: application/json
{ "url": "https://your-crm.example/webhooks/perfektpak", "events": ["order.status_changed"], "description": "CRM — статусы заказов" }

Полный протокол регистрации (проверка URL, секрет, подпись) — на странице «Вебхуки». Ниже — то, что специфично именно для статусов заказов.

Обработать событие: previous_status

У событий *.status_changed поле data несёт не только сам объект, но и previous_status — статус, из которого заказ вышел. Это нужно, чтобы отличить, например, «собирается → в доставке» от «собирается → отменён», не храня историю статусов у себя отдельно:

{
  "id": "evt_9f2a1c",
  "type": "order.status_changed",
  "occurred_at": "2026-09-05T11:20:00+03:00",
  "data": {
    "previous_status": "ASSEMBLING",
    "object": {
      "id": "o_5f6a7b8c9d", "marketplace": "WB", "external_id": "WB-88213456",
      "status": "IN_DELIVERY", "status_label": "В доставке",
      "marketplace_status": { "supplier": "complete", "buyer": "sold", "changed_at": "2026-09-05T11:20:00+03:00" },
      "article": "COFFEE-1KG-BRIZ", "barcode": "4680012340015", "nm_id": 123456789, "is_b2b": false,
      "shipment_id": "s_wb_2026_0825_12", "shipment_external_id": "WB-1082345671",
      "created_at": "2026-08-25T11:20:00+03:00", "packed_at": "2026-08-25T15:40:00+03:00",
      "synced_at": "2026-09-05T11:20:00+03:00"
    }
  }
}
def handle_order_status_changed(event):
    order = event["data"]["object"]
    previous = event["data"]["previous_status"]
    if order["status"] == "CANCELLED" and previous != "CANCELLED":
        notify_support(order, reason="cancelled")
    update_crm_card(order["external_id"], status=order["status"])

Резервный опрос

Доставка вебхука занимает время, а после восьмой подряд неудачной попытки подписка автоматически отключается. Держите резервный опрос — редкий, но достаточный, чтобы заметить «подписка отключилась и никто не увидел»:

import datetime

since = last_successful_sync()  # ваша метка времени, не значение API
page = requests.get(
    "https://api.perfektpak.ru/v1/orders",
    params={"created_from": since.isoformat(), "created_to": datetime.datetime.now().isoformat(), "limit": 1000},
    headers={"Authorization": f"Bearer {PP_KEY}"},
).json()
for order in page["items"]:
    reconcile(order)  # та же функция обновления карточки, что и у обработчика вебхука

Раз в час резервного опроса достаточно для большинства интеграций; если вебхук работает штатно, опрос почти всегда не найдёт расхождений — это ожидаемо, а не признак того, что опрос лишний.

Идемпотентность по id события

Ретраи доставки (см. расписание на странице «Вебхуки») могут прислать одно и то же событие дважды — например, если ваш обработчик ответил успешно, но соединение оборвалось раньше, чем мы это увидели. Каждое событие несёт уникальный id (evt_...) — сохраняйте обработанные id (последние сутки достаточно) и пропускайте повтор:

def on_webhook(event):
    if already_processed(event["id"]):
        return  # ретрай того же события — не ошибка, просто не делаем работу дважды
    handle_order_status_changed(event)
    mark_processed(event["id"])

Что дальше

Заказ входит в отгрузку — если вам важен именно момент, когда маркетплейс принял всю партию, а не отдельный заказ, смотрите гайд «Отгрузки: узнать, что поставку приняли на СЦ».