Разработка · 6 мин чтения

Вебхуки СДЭК: как ловить статусы заказа в реальном времени

Вебхуки СДЭК статусы заказа получаю в бэкенде за секунды после того, как курьер отсканировал посылку или клиент отказался от получения. Пока я не подключил вебхуки на паре интернет-магазинов на WooCommerce, статус заказа обновлялся через крон-задачу, которая раз в 10-15 минут дёргала метод трекинга СДЭК по каждому активному заказу. При 200-300 заказах в день это упирается в лимиты API и создаёт задержку, за которую клиент успевает написать в поддержку «где мой заказ». Вебхук закрывает эту проблему одним HTTP-запросом от СДЭК на мой эндпоинт в момент смены статуса.

Зачем вебхуки СДЭК статусы заказа, а не опрос API по крону

Разница между двумя подходами не в удобстве кода, а в конкретных цифрах, которые я вижу на живых проектах.

Опрос по расписанию Вебхук
Задержка до появления статуса от 5 до 15 минут, зависит от частоты крона обычно 1-5 секунд после события у СДЭК
Нагрузка на лимит запросов СДЭК растёт линейно с числом активных заказов не зависит от количества заказов
Что нужно на своей стороне крон-задача и сравнение старого статуса с новым в базе публичный HTTPS-эндпоинт и обработчик входящих запросов

При росте магазина опрос по крону начинает упираться в лимит запросов к API СДЭК, а часть заказов обновляется с задержкой, потому что крон обрабатывает их по очереди. Вебхук снимает эту зависимость от числа активных заказов полностью.

Как подключить вебхук в личном кабинете и через API v2

Доступ к вебхукам идёт через тот же интеграторский аккаунт, что и обычные методы API v2. Порядок такой:

  • получаю client_id и client_secret в личном кабинете СДЭК в разделе интеграции;
  • меняю их на токен через метод /v2/oauth/token?grant_type=client_credentials;
  • регистрирую вебхук POST-запросом на /v2/webhooks, указывая свой URL и тип события;
  • проверяю список активных подписок методом GET на тот же путь;
  • удаляю подписку через DELETE по её uuid, если меняю эндпоинт.
curl -X POST https://api.cdek.ru/v2/webhooks \
  -H "Authorization: Bearer $CDEK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/cdek/8f21c4a9",
    "type": "ORDER_STATUS"
  }'

URL обязан быть доступен по HTTPS с валидным сертификатом, самоподписанные СДЭК не принимает. На своём хостинге проверяю это заранее внешним запросом, а не полагаюсь на то, что сертификат просто «стоит».

Какие события шлёт СДЭК и что приходит в теле запроса

Основных типов событий четыре, и в обработчике я развожу их сразу по разным веткам логики.

Событие Когда приходит Что делаю с ним
ORDER_STATUS смена статуса заказа: принят, передан курьеру, в пункте выдачи, вручён, возврат обновляю статус в CRM или магазине, шлю уведомление клиенту
PRINT_FORM готова печатная форма для заказа подтягиваю ссылку на форму для склада
DOWNLOAD_PHOTO доступно фото документа при вручении сохраняю ссылку на фото к заказу как доказательство вручения

В теле запроса всегда есть uuid самого события, type, date_time и блок attributes, где лежат cdek_number, номер заказа в моей системе, код и дата статуса. Именно по uuid я потом отсекаю повторные доставки одного и того же события.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Проверка подписи и защита эндпоинта от чужих запросов

В отличие от эквайринга, где T‑Bank подписывает каждое уведомление токеном на основе пароля терминала и я просто сверяю хеш, у СДЭК встроенной подписи тела запроса нет. Значит защиту эндпоинта строю сам, тремя слоями:

  • вместо предсказуемого пути вроде /webhooks/cdek использую URL со случайным сегментом длиной 16-32 символа, который никто не угадает перебором;
  • перед тем как менять статус заказа у себя, делаю обратный запрос к /v2/orders/{uuid} и сверяю номер заказа с тем, что пришло в вебхуке. Если данные не совпадают, запрос игнорирую;
  • отвечаю кодом 200 сразу после постановки события в очередь, а не после завершения всей обработки. СДЭК повторяет доставку, если не получил быстрый ответ, и без дедупликации по uuid один и тот же статус улетает клиенту дважды.
app.post('/hooks/cdek/8f21c4a9', async (req, res) => {
  const { uuid, type, attributes } = req.body;

  if (await isProcessed(uuid)) {
    return res.sendStatus(200);
  }

  const order = await cdekApi.getOrder(attributes.uuid);
  if (!order || order.number !== attributes.number) {
    return res.sendStatus(403);
  }

  await queue.add('cdek-status', { type, attributes });
  await markProcessed(uuid);

  res.sendStatus(200);
});

Интеграция вебхуков СДЭК в Tilda, WooCommerce и n8n

Tilda

Сама Тильда вебхуки не принимает и статусов заказов у себя не хранит дольше базового вида заказа. Обычно я ставлю перед ней отдельный обработчик на своём сервере или в виде serverless-функции, который получает событие от СДЭК и пишет статус в подключённую CRM (Bitrix24, amoCRM), откуда он уже попадает в переписку с клиентом. Такая связка «Тильда плюс CRM плюс приём вебхуков» это не разовая правка виджета, а отдельная интеграция, я оцениваю подобные проекты со связкой CRM, эквайрингом и СДЭК от 40 000 ₽, потому что там появляется свой сервер, очередь и обработка ошибок доставки событий.

WooCommerce

Готовые плагины СДЭК для WooCommerce чаще всего тянут статус по крону, а вебхук встраиваю отдельным REST-роутом через register_rest_route, в обработчике которого меняю статус заказа и добавляю трек-номер в примечание к заказу. Это снимает часть нагрузки с крона и ускоряет обновление статуса в личном кабинете покупателя.

n8n

Для сценариев без своего бэкенда вебхук СДЭК принимаю нодой Webhook в n8n, дальше по типу события развожу через Switch: обновление статуса иду писать в CRM через HTTP Request, уведомление клиенту отправляю в aiogram-бота или в чат поддержки. Если через workflow проходят ФИО, телефон и адрес получателя, держу n8n на своём сервере в России, а не в облачном n8n.cloud за рубежом, чтобы персональные данные клиентов не оказались на чужой инфраструктуре за пределами страны. Когда интеграция обрастает несколькими системами сразу, обычно проще заказать сборку такого сценария целиком, чем собирать её из десятка нод самому, для этого у меня есть услуги по автоматизации и интеграции сервисов.

Частые ошибки при обработке статусов заказа СДЭК

  • отвечать 200 только после отправки уведомления клиенту: если письмо или сообщение в Telegram зависает на секунду дольше таймаута, СДЭК шлёт повтор, и обработка задваивается;
  • принимать вебхук на угадываемом URL без сверки номера заказа через API, тогда любой, кто найдёт путь, может подсунуть фиктивный статус;
  • хранить только последний статус без истории смен, из-за чего при споре с клиентом нечем показать, когда именно заказ ушёл в возврат;
  • интерпретировать статусы линейно, не закладывая ветку возврата, когда заказ после неудачной попытки вручения снова уходит на склад;
  • забыть выключить старый крон-опрос после перехода на вебхуки, из-за чего клиент получает одно и то же уведомление дважды из разных источников.

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

Нужен ли отдельный тариф API СДЭК, чтобы получать вебхуки

Нет, доступ к вебхукам входит в обычный интеграторский доступ к API v2. Отдельно оплачивать эту функцию не нужно, достаточно действующего договора с СДЭК и client_id с client_secret в личном кабинете.

Что делать, если вебхук перестал приходить

Сначала проверяю доступность своего эндпоинта снаружи, а не из локальной сети или из-за VPN, и валидность сертификата. Дальше смотрю список активных подписок через GET-запрос к /v2/webhooks, возможно URL поменялся при переносе сервера. Пока чиню приём, временно включаю резервный опрос статуса раз в 15-20 минут, чтобы не терять обновления.

Можно ли получать вебхуки СДЭК без своего сервера, только на Tilda

Нет, сама Тильда не принимает входящие вебхуки. Нужен внешний обработчик, хотя бы одна serverless-функция, которая примет запрос от СДЭК и запишет статус туда, откуда его дальше показывает Тильда, обычно в подключённую CRM.

Как отличить тестовый вебхук от боевого

В личном кабинете СДЭК есть тестовый контур со своим client_id, вебхуки из него приходят в том же формате, что и боевые. Поэтому в обработчике сверяю номер заказа с базой через дозапрос к API, тестовые заказы там просто не найдутся и отсеются сами.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно