MAATRIX / Блог / Партнёр отключил старую версию API без предупреждения: шесть часов простоя

Партнёр отключил старую версию API без предупреждения: шесть часов простоя

MAATRIX

Интернет-магазин интегрирован с внешним API службы доставки: создаёт отправления, получает трек-номера, дёргает статусы. Всё работало два года без единого прикосновения к этому куску кода — а потом за одно утро перестало работать вообще, без единого предупреждения от партнёра. Разбираем инцидент по шагам: что видели в логах, какие версии происходящего отбросили и почему настоящая причина обнаружилась не в мониторинге, а в папке "Спам".

Первые сигналы: тикеты раньше алертов

Первым о проблеме узнал не мониторинг, а поддержка. В 9:20 утра по Москве в чат посыпались обращения: "заказ оформлен, а трек-номер не пришёл", "статус доставки не обновляется третий час". Дежурный инженер открыл админку — там на созданных за ночь заказах статус интеграции с доставкой висел в состоянии pending, хотя обычно трек-номер прилетает за 2-5 секунд после оформления заказа.

Общий мониторинг доступности сайта (пинг главной страницы, проверка 200 OK) при этом был зелёным — сайт открывался, каталог работал, оформление заказа проходило. Проблема была локализована в одном конкретном узле: воркер, который синхронно вызывает API службы доставки при создании заказа, начал массово падать по таймауту или получать ошибку от партнёра.

Этот случай — хороший повод свериться с тем, как вообще устроен мониторинг: если проверка доступности бьёт только по главной странице, она в принципе не может увидеть, что сломался конкретный API-интеграционный путь, который не отражается на HTTP-коде витрины.

Что показывали логи и метрики

Первым делом подняли логи воркера интеграции за последний час:

2026-08-27 09:03:12 ERROR shipping_client: request failed
  url: https://api.partner-delivery.example/v1/shipments
  status: 404
  body: {"error":"not_found","message":"Endpoint does not exist"}
2026-08-27 09:03:12 ERROR shipping_client: retry 1/3 scheduled in 2s
2026-08-27 09:03:14 ERROR shipping_client: request failed
  url: https://api.partner-delivery.example/v1/shipments
  status: 404
2026-08-27 09:03:16 ERROR shipping_client: retries exhausted, order marked pending_manual

Ключевая деталь, на которую сразу не обратили внимания: не 500 и не таймаут, а честный 404 Not Found с телом endpoint does not exist. Это не «сервис партнёра лежит» — это «такого адреса больше нет». Разница принципиальная, но в первые минуты инцидента, когда в чате уже штук пятнадцать тикетов от поддержки, на неё смотрят вскользь и начинают перебирать более привычные версии.

Метрики из Grafana (Prometheus-таргет на воркере) показали резкий скачок счётчика shipping_api_errors_total ровно в 09:00 — до этого линия была ровной неделями, а тут вертикальная стена. Ошибки шли не волнообразно, как бывает при перегрузке или частичной деградации партнёра, а разом на все 100% запросов начиная с одной секунды. Это уже намёк, что дело не в нагрузке и не в сетевой нестабильности — переключатель щёлкнул одномоментно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Гипотеза первая: у нас проблема с сетью или сертификатами

Первая рабочая версия — что-то не так с сетью на нашей стороне: сгорел сертификат, истёк срок действия TLS, поменялся исходящий IP после недавнего апдейта на сервере, или партнёр забанил наш IP по какой-то причине.

Проверили руками с сервера:

curl -v https://api.partner-delivery.example/v1/shipments \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"test"}'

TLS-хендшейк прошёл штатно, сертификат партнёра валиден, соединение установилось без проблем. Ответ пришёл содержательный — тот же 404 с телом про несуществующий endpoint, а не обрыв соединения или 403 Forbidden, которые были бы при бане по IP. DNS резолвился корректно, dig api.partner-delivery.example отдавал тот же адрес, что и всегда. Проверили с другого сервера (той же сети, другого дата-центра) — идентичный результат. Версию с сетью и TLS отбросили в течение первых десяти минут: раз партнёр вообще отвечает содержательным JSON с корректной структурой ошибки, значит, сеть и авторизация до него доходят нормально, просто конкретный маршрут не существует.

Гипотеза вторая: сломал недавний деплой

Следующая по вероятности версия — не партнёр, а мы сами. Проверили git log по каталогу с клиентом интеграции и общий деплой-лог за последние трое суток:

git log --since="72 hours ago" --oneline -- src/integrations/shipping/

Пусто. Последний коммит в этом модуле был больше месяца назад — рефакторинг логирования, к формированию URL не относился вообще. Проверили общий деплой-пайплайн (CI/CD) — да, релизы были, но они касались фронтенда и модуля скидок, к интеграции с доставкой не притрагивались. Проверили ещё и конфиги на сервере: переменные окружения SHIPPING_API_URL, SHIPPING_API_KEY не менялись — сверили хэш файла .env с бэкапом недельной давности через md5sum, совпал.

Отдельно проверили версию используемой библиотеки-клиента (обёртка над requests/httpx), которую поставляет сама служба доставки как SDK. Файл requirements.lock не менялся, версия зафиксирована жёстко, а не через >= — то есть это не тот случай, когда тег latest незаметно подтянул новую версию и сломал контракт. Здесь версия зависимости стояла на месте буквально. Версию «мы сами что-то сломали» отбросили примерно за 20 минут: ни код, ни конфиг, ни зависимости не менялись.

Настоящая причина: партнёр закрыл версию API, о которой все забыли

К этому моменту оставалась одна разумная гипотеза: изменилось что-то на стороне партнёра. Проверили публичный статус-пейдж службы доставки — там всё зелёное, "All systems operational". Это на секунду сбило с толку: если у них всё работает, а у нас 404 — значит, ломается что-то посередине? Но статус-пейдж отражает работоспособность их системы в целом, а не конкретной версии API, которой мы пользуемся.

Дальше — прямой звонок в поддержку партнёра, и там прозвучала фраза, из-за которой всё встало на места: "а вы разве не видели письмо про сворачивание API v1?". Проверили почтовый ящик, на который зарегистрирован аккаунт в личном кабинете партнёра — письмо действительно было, отправлено за 90 дней до инцидента, тема "Important: API v1 deprecation notice — sunset date August 27, 2026". Письмо попало в папку "Промоакции" фильтром Gmail и его никто не открыл. Второе аналогичное письмо-напоминание, отправленное за две недели до отключения, постигла та же участь.

Реальная причина инцидента: партнёр действительно предупреждал о сворачивании старой версии API, но письмо ушло на общий адрес интеграционного аккаунта, а не в канал, за которым реально следит дежурный инженер. В назначенную дату — 27 августа — версия v1 была отключена полностью на инфраструктуре партнёра: все запросы на /v1/* стали возвращать 404, при этом v2 работал прекрасно и все эти два года был доступен параллельно. Мы просто никогда не переключались, потому что "и так работает".

Восстановили примерную хронологию инцидента:

ВремяСобытие
90 дней доПисьмо о sunset API v1 ушло в спам-фильтр Gmail
14 дней доПовторное письмо-напоминание — та же судьба
09:00Партнёр отключает v1, все запросы получают 404
09:03Первые ошибки в логах воркера, счётчик ошибок уходит в потолок
09:20Первые тикеты от поддержки
09:30Отброшена версия про сеть/TLS
09:50Отброшена версия про свежий деплой
~10:30Звонок в поддержку партнёра, найдена причина
~15:00Экстренный переход на v2, инцидент закрыт

Как чинили: экстренный переход на v2 за шесть часов

Хорошая новость — партнёр не удалил документацию по v2, она была доступна в личном кабинете всё это время. Плохая новость — v2 отличался от v1 не косметически: другая схема авторизации, другие поля в теле запроса, другой формат ответа.

# v1 (отключён)
POST /v1/shipments
Authorization: Bearer <api_key>
{
  "order_id": "12345",
  "address": "г. Москва, ул. Ленина, 10"
}

# v2 (актуальный)
POST /v2/shipments
X-Api-Key: <api_key>
X-Api-Secret: <api_secret>
{
  "external_id": "12345",
  "delivery_address": {
    "city": "Москва",
    "street": "ул. Ленина",
    "house": "10"
  }
}

Пришлось не просто поменять URL, а переписать сериализацию адреса из плоской строки в структурированный объект, добавить второй секрет в конфиг, и главное — обработать другой формат ответа с трек-номером (в v2 он лежит не в корне JSON, а во вложенном объекте shipment.tracking). На это ушло около двух часов разработки и тестирования на стейджинге против песочницы партнёра (у них, к счастью, была отдельная тестовая среда для v2 с теми же учётными данными формата, что и на проде).

Отдельно нужно было разобрать очередь из заказов, которые за время инцидента попали в pending_manual — примерно за пять с половиной часов накопилось прилично необработанных отправлений. Написали разовый скрипт, который прогонял их через новый клиент v2 пачками, с паузой между запросами, чтобы не упереться в лимит партнёра по частоте запросов.

Деплой хотфикса выкатили в обход обычного пайплайна с полным набором проверок — сознательно, под инцидент, с последующим ревью постфактум. Через несколько минут после деплоя счётчик shipping_api_errors_total в Grafana вернулся к нулю, статусы заказов начали проставляться штатно. С момента первого тикета от поддержки до полного закрытия инцидента прошло около шести часов.

Что изменили после инцидента

Постмортем дал три конкретных изменения в процессах и инфраструктуре, а не просто «будем внимательнее».

Во-первых, письма от партнёров с интеграциями теперь идут не на личный почтовый ящик одного инженера, а на общий адрес команды с отдельным фильтром "не спам, не архивировать автоматически" и еженедельным ревью входящих раз в понедельник — специально ради писем такого рода, которые не требуют немедленной реакции, но требуют, чтобы их вообще прочитали.

Во-вторых, добавили синтетическую проверку самого API-эндпоинта партнёра, а не общего аптайма сайта — отдельный чек в Uptime Kuma, который раз в 15 минут дергает v1-путь тестовым запросом и алертит, если код ответа не 200/201, а не только если сервер недоступен вообще. Логика такого чека проще, чем у полноценного canary-теста, но она поймала бы этот сбой за 15 минут, а не за 20+ минут перебора гипотез. Про то, как вообще устроить алерты так, чтобы их читали, а не игнорировали, писали отдельно — если у вас мониторинг настроен, но на самом деле его никто не смотрит, проблема ровно та же, что и с письмом в спаме: сигнал был, но не дошёл до человека.

В-третьих, в код интеграции добавили явную привязку к номеру версии API как к конфигурируемому параметру, а не к захардкоженному пути в коде — теперь смена v1 на v2 требует правки одной переменной окружения плюс адаптера форматов, а не поиска всех мест в коде, где зашит путь /v1/. Заодно завели табличку с датами договорных обязательств по всем внешним интеграциям (доставка, платёжный шлюз, SMS-агрегатор) — у каждой партнёрской версии API есть примерный жизненный цикл, и полезно знать заранее, у кого он подходит к концу, а не узнавать об этом по факту отключения.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как понять, что 404 от партнёра — это "версия отключена", а не "у нас баг в пути"?

Сравните с последним рабочим состоянием: если путь и тело запроса не менялись у вас, а ответ вдруг стал 404 с телом вида "endpoint does not exist" или "version not supported" — это почти всегда сигнал со стороны партнёра, а не локальная ошибка. Проверьте статус-код и тело ошибки внимательно, а не полагайтесь только на факт "запрос не прошёл".

Стоит ли держать интеграцию сразу на двух версиях API партнёра параллельно?

Если партнёр это позволяет и у вас есть время — да, это снижает риск ровно такого инцидента: переключение на резервную версию делается быстрее, чем экстренная разработка адаптера с нуля. Но постоянно поддерживать два клиента ради одной интеграции оправдано не всегда — решение зависит от критичности конкретного партнёра для бизнеса.

Как не пропустить письмо о sunset API в следующий раз?

Заведите отдельный технический почтовый ящик для регистрации во всех партнёрских личных кабинетах (не личную почту сотрудника), настройте фильтр так, чтобы письма от известных доменов партнёров никогда не попадали в спам или промоакции, и назначьте человека, который раз в неделю просматривает входящие в этом ящике целиком, а не только непрочитанные с высоким приоритетом.

Нужен ли отдельный health-check на каждую внешнюю интеграцию, а не только на сайт в целом?

Для интеграций, от которых зависит критичный бизнес-процесс (оформление заказа, оплата, отправка), да — общий аптайм сайта и здоровье конкретного внешнего API это разные вещи, и мониторинг витрины физически не может увидеть, что сломался конкретный путь синхронизации с партнёром.

Как быстро восстановить заказы, которые зависли в очереди во время простоя интеграции?

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

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →