Ошибка выглядит так. Интеграция отправляет POST на ваш эндпоинт. Эндпоинт работает, маршрут на месте, руками всё проверяется. Но в логах — GET на маршрут, который принимает только POST, и ответ 404 или 405.

Причина почти всегда одна: между клиентом и эндпоинтом стоит редирект с кодом 301 или 302. И эти коды не обязывают клиента сохранить метод.

Проверить можно за десять секунд:

curl -sS -o /dev/null -L -d 'test=1' \
  -w 'метод после редиректа: %{method}\n' \
  'https://httpbingo.org/redirect-to?url=/anything&status_code=301'

Вывод: метод после редиректа: GET. Тело запроса при этом тоже теряется.

Замените 301 на 308 в той же команде — получите POST. Разница только в коде ответа.

Пять кодов и два свойства

Редиректы различаются по двум независимым признакам: постоянный он или временный, и сохраняется ли метод запроса.

Код Название Постоянство Метод сохраняется
301 Moved Permanently постоянный нет
302 Found временный нет
303 See Other нет, всегда GET
307 Temporary Redirect временный да
308 Permanent Redirect постоянный да

Обратите внимание на симметрию: 307 — это 302 с гарантией метода, а 308 — это 301 с гарантией метода.

Откуда взялась эта путаница

Формально спецификация никогда не требовала менять метод при 301 и 302. Но ранние браузеры и клиенты делали именно это, а к моменту, когда несоответствие заметили, так работал уже весь веб.

В итоге поведение закрепили как допустимое: RFC 9110, действующая спецификация семантики HTTP, разрешает клиенту заменить POST на GET при 301 и 302. Разрешает, но не требует — то есть поведение остаётся на усмотрение клиента, и полагаться на него нельзя.

Чтобы дать однозначный вариант, ввели 307 и 308. Для них замена метода запрещена: клиент обязан повторить запрос тем же методом и с тем же телом. Код 308 описан в отдельном RFC 7538, справочное описание есть и в документации MDN.

Практический вывод: 301 и 302 годятся только там, где запрос заведомо GET. Везде, где может прийти POST, нужны 307 или 308.

Вторая ловушка: постоянные редиректы кэшируются

Про эту забывают чаще, чем про метод.

301 и 308 объявляют перенос постоянным, и браузер имеет право закэшировать их надолго — иногда до очистки кэша пользователем. Сервер после этого просто не спрашивают.

Отсюда правило: ошибочный 301 почти невозможно откатить. Вы поправите конфигурацию, но у посетителей, успевших получить старый ответ, редирект продолжит работать. У 302 и 307 такой проблемы нет — они временные по определению.

Практический совет: пока схема переездов не устоялась, ставьте временные коды. Перевести 302 в 301 позже легко, обратно — нет.

Что выбирать

Страница переехала навсегда, принимает только GET → 301. Классический случай: старый URL статьи, смена структуры адресов.

То же самое, но эндпоинт принимает POST → 308. Сюда попадают API, вебхуки, формы.

Адрес меняется временно, только GET → 302.

Временно, и метод обязан сохраниться → 307.

После POST нужно отправить пользователя на страницу результата → 303. Это шаблон POST/Redirect/GET: он специально превращает запрос в GET, чтобы обновление страницы не отправляло форму повторно. Здесь смена метода — не баг, а цель.

Где это ломается на практике

Редирект http→https на API. Самый частый и самый неприятный случай. Клиент настроен на http://, сервер отвечает 301 на https://, метод теряется. Симптом: GET в логах на POST-маршруте. Лечится заменой 301 на 308.

Канонизация хоста. Редирект www → без www (или наоборот) обычно ставят как 301. Для страниц это нормально, но если на том же хосте живёт API, действует ровно та же проблема.

Отправка форм. Форма отправляется на адрес, который редиректит. Пользователь видит, что «ничего не произошло», потому что данные до обработчика не дошли.

Вебхуки. Внешний сервис шлёт POST по адресу, который вы когда-то дали. Если адрес переехал через 301, вебхуки молча перестают работать, а в логах отправителя — успешный ответ на редирект.

Общий признак у всех четырёх: эндпоинт исправен, а запрос до него не доходит в исходном виде. Поэтому диагностика уходит не туда — ищут ошибку в коде обработчика, а она в конфигурации веб-сервера.

Почему сокращатели ссылок используют 302

Короткая ссылка почти всегда отдаёт 302, и это осознанный выбор, а не небрежность.

Причина в постоянстве, а не в методе: у короткой ссылки адрес назначения может измениться. Поставить 301 значило бы разрешить браузерам закэшировать переход навсегда — и после смены цели часть посетителей продолжала бы попадать на старый адрес. Метод здесь роли не играет: по коротким ссылкам ходят GET-запросами.

Как устроен сам механизм, разобрано отдельно в статье про то, как работают сокращатели ссылок.

Чего выбор кода не меняет

Два распространённых заблуждения.

Ссылочный вес. Выбор между 301 и 302 не влияет на передачу веса — Google подтвердил это ещё в 2016 году. Подробный разбор — в материале о том, передаётся ли ссылочный вес через короткую ссылку.

Выбор канонического адреса. Редирект и rel="canonical" решают разные задачи: редирект убирает адрес из обращения, canonical оставляет оба доступными и указывает главный. Когда что применять, описано в руководстве по canonical.

Короткий чек-лист

  • На маршрутах, принимающих POST, использовать 307 или 308, а не 302 и 301.
  • Проверять редирект http→https отдельно: он часто задан общим правилом и про API забывают.
  • Пока схема не устоялась — временные коды. 301 и 308 кэшируются и почти не откатываются.
  • После обработки формы — 303, это не ошибка, а нужное поведение.
  • Цепочки сокращать до одного перехода: каждый лишний прыжок добавляет и задержку, и риск.