К содержанию
ActionPulse

Запрос из браузера блокируется

Как отличить настоящую ошибку CORS от отказа по origin, от политики безопасности содержимого и от блокировщика — и что именно исправлять в каждом случае.

Кому
Фронтенд-разработчикам
Проверено
На этой странице

Под «блокируется CORS» на практике скрываются пять разных вещей, и лечатся они по-разному. Четыре из них видны в консоли браузера, а пятая — самая коварная — в консоли не видна вообще: запрос уходит, получает 403 и тихо теряется.

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

Что это на самом деле

Что видно Кто отказал Куда идти
Сообщение в консоли про политику общего доступа, ответа в сетевой панели нет браузер: у ответа нет разрешения заблокирован политикой общего доступа
Сообщение в консоли про предварительный запрос браузер: OPTIONS не разрешил основной запрос не прошёл предварительный запрос
В сетевой панели 403, в консоли тихо сервер: origin не в списке токена отказ по origin
Сообщение про Content Security Policy браузер: ваша собственная политика страницы блокировка политикой безопасности содержимого
Запроса нет вовсе или он отменён без ответа расширение или блокировщик блокировка расширением или блокировщиком

Полезно знать заранее: запросы приёма событий отвечают разрешением для любого домена. Транспорт намеренно открыт, а решение принимает сервер — по типу ключа, области действия и точному origin. Поэтому настоящая ошибка CORS на /v1/track почти всегда означает, что запрос ушёл не туда или не дошёл до ActionPulse.

Заблокирован политикой общего доступа

Вероятная причина. Ответ, который получил браузер, не несёт заголовка с разрешением для вашего origin. Варианты по убыванию частоты:

Причина Признак
Запрос ушёл на адрес кабинета вместо адреса приёма в ответе 404, путь /v1/track без базы приёма
Перед приёмом стоит свой прокси или CDN, который срезает заголовки ответа в ответе нет Access-Control-Allow-Origin, хотя запрос дошёл
Запрос идёт к пути вне публичного набора это /v1/t.js, /v1/t-replay.js, /v1/t-diag.js, /v1/t-picker.js или /healthz
Запрос отправлен с credentials: 'include' приём никогда не выставляет разрешение на передачу cookie

Публичное разрешение для любого домена действует на пять адресов записи (/v1/track, /v1/identify, /v1/alias, /v1/replay, /v1/picker/capture), на публичную конфигурацию сбора /v1/cfg и на версионные файлы SDK вместе с манифестом. Совместимый адрес скрипта и проверка живости в этот набор не входят — для них заголовки задаёт конфигурация установки.

Как проверить. Запросите тот же адрес из терминала с заголовком Origin и посмотрите заголовки ответа:

curl -sS -o /dev/null -D - \
  -X POST "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Origin: https://example.com" \
  -H "Authorization: Bearer YOUR_BROWSER_TOKEN" \
  -d '{"batch":[]}'

Ожидаемо: Access-Control-Allow-Origin: *, Vary: Origin и статус 422 (batch is empty) — значит транспорт в порядке, ключ принят, а запрос ничего не записал. Если заголовка разрешения нет — ответ пришёл не от приёма.

Безопасное исправление. Сверьте адрес приёма с экраном установки трекера в кабинете: у приёма своя база, он не живёт под /api/v1. В теге это атрибут data-api-host, в вызове инициализации — параметр хоста. Если перед приёмом стоит ваш прокси, настройте его на проброс заголовков ответа без изменений. Cookie приёму не нужны: уберите credentials: 'include'.

Если не помогло. Проверьте, не переписывает ли промежуточный узел код ответа: ошибка от прокси приходит без конверта error и без заголовков разрешения, из-за чего браузер сообщает про CORS, хотя причина — отказ прокси.

Не прошёл предварительный запрос

Вероятная причина. Браузер сначала спрашивает разрешение запросом OPTIONS, и основной запрос не уходит, пока ответ его не разрешит. Причины:

Причина Признак
К запросу добавлен свой заголовок в консоли назван заголовок, которого нет в разрешённом списке
Метод не POST разрешены только POST и OPTIONS
OPTIONS не доходит: прокси, WAF или балансировщик его не пропускают в сетевой панели OPTIONS с ошибкой, 403, 405 или без ответа
OPTIONS отправлен на совместимый адрес скрипта у /v1/t.js зарегистрирован только GET, поэтому OPTIONS даёт 405; у версионных файлов он есть и отвечает 204

Разрешённый список заголовков фиксирован и никогда не расширяется по запросу браузера: Authorization, Content-Type, X-PP-Tracker, X-PP-Source, X-PP-SDK-Name, X-PP-SDK-Version, X-PP-App-Version, X-PP-Release.

Как проверить. Повторите предварительный запрос руками и сравните запрошенные заголовки с разрешёнными:

curl -sS -i -X OPTIONS "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Origin: https://example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization,content-type"

Ожидаемо 204 и Access-Control-Allow-Headers с перечисленным выше списком.

Безопасное исправление. Уберите свои заголовки из запросов к приёму — идентификатор трассировки, X-Requested-With и подобные. Всё, что нужно передать, кладите в свойства события. Если OPTIONS не пропускает ваш периметр, разрешите метод OPTIONS для путей приёма: он не несёт данных и отвечает до авторизации.

Если не помогло. Учтите, что предварительный запрос есть не у всех отправок. На выходе со страницы обычные события уезжают простым запросом с типом text/plain и токеном в теле — предварительной проверки у него нет вовсе, а результат недоступен странице. Если события теряются только при уходе со страницы, дело не в предварительном запросе.

Отказ по origin

Вероятная причина. Транспорт разрешён, а сервер отказал: origin страницы не совпал со списком, заданным при выпуске браузерного токена.

{ "error": { "code": "credential_origin_forbidden",
             "message": "browser credential origin is not allowed", "details": {} } }

Это не ошибка CORS. Ответ приходит с разрешением для любого домена, поэтому страница его читает и браузер молчит. Трекер такой ответ не повторяет и считает партию доставленной: события исчезают без единой записи в консоли. Сравнение идёт на точное совпадение, маски не поддерживаются, а поддомен, схема и непустой порт дают разные origin.

Отдельный признак того же отказа: публичная конфигурация сбора отвечает 200 и «всё выключено» — автосбор, записи сессий и диагностика выключены. Так же выглядит ответ для неизвестного, отозванного или истёкшего ключа: существование ключа не раскрывается.

Как проверить. Во вкладке «Сеть» откройте неудачный запрос на /v1/track и посмотрите заголовок запроса Origin — это точное значение, с которым идёт сравнение. Затем сравните его со столбцом «Scopes / origins» в разделе «Credentials проекта» настроек проекта. Тот же отказ воспроизводится из терминала:

curl -sS -o - -w '\n%{http_code}\n' \
  -X POST "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Origin: https://example.com" \
  -H "Authorization: Bearer YOUR_BROWSER_TOKEN" \
  -d '{"batch":[]}'

Подставьте свой origin: 422 — origin принят, 403 — нет.

Безопасное исправление. Список origin у выпущенного токена не редактируется — ни правкой, ни ротацией. Выпустите новый браузерный токен, перечислив все адреса сразу: production, варианты с www и без, поддомены со своими страницами, адрес предпросмотра сборок. Замените значение в теге, убедитесь, что события идут, и только потом отзовите старый токен.

Если не помогло. Посмотрите, не приходит ли Origin: null. Так выглядят непрозрачные origin: страница из локального файла, песочница iframe без allow-same-origin, документ из data:-адреса. Такой origin не совпадает ни с одним адресом в списке и всегда даёт 403. Полный разбор причин — в ключах и origin.

Блокировка политикой безопасности содержимого

Вероятная причина. Отказывает не ActionPulse, а политика вашей же страницы. Директив нужно три, и каждая ломается по-своему:

Директива Что перестаёт работать Сообщение в консоли
script-src без origin файлов трекера ничего не загружается вовсе отказ загрузить скрипт
connect-src без origin приёма скрипт работает, события не уходят отказ подключиться к адресу
worker-src без blob: только записи сессий отказ создать воркер

Отдельный случай — политика с одноразовым числом (nonce) или с хешами: дополнительные модули трекера подключаются динамически, своего nonce у них нет, поэтому основной файл загружается, а автосбор, записи и диагностика молча не включаются. Ошибка загрузки модуля не является ошибкой сбора: базовые события продолжают идти.

Как проверить. Откройте консоль и найдите сообщения про Content-Security-Policy: в них названа и нарушенная директива, и адрес. Проверьте по сетевой панели, что загрузилось: файл ядра, файлы модулей, запрос /v1/cfg, запрос /v1/track. Первый отсутствующий в этой цепочке и есть заблокированный.

Безопасное исправление. Разрешите ровно то, что нужно, не ослабляя политику целиком:

script-src 'self' https://YOUR_ACTIONPULSE_HOST;
connect-src 'self' https://YOUR_ACTIONPULSE_HOST;
worker-src 'self' blob:;

Для политики с nonce добавьте origin файлов трекера в script-src явно либо используйте 'strict-dynamic', который распространяет доверие на скрипты, добавленные уже доверенным скриптом. Директива unsafe-eval не нужна ни при каких условиях: в бандлах нет ни eval, ни new Function. Новую политику удобно сначала выкатить в режиме отчётов, а не выключать сбор.

Если не помогло. Если файлы вы отдаёте со своего домена, в script-src достаточно 'self', но connect-src всё равно должен содержать адрес приёма: события уходят на приём, а не на ваш домен. И проверьте worker-src отдельно — про него забывают чаще всего, а симптом обманчиво частичный.

Блокировка расширением или блокировщиком

Вероятная причина. Запрос не отклонён — он не создан. Блокировщики рекламы и трекинга работают по спискам адресов и отменяют запрос до отправки; часть расширений дополнительно вырезает сам файл скрипта из разметки.

Отличительные признаки: запроса нет в сетевой панели вовсе или он помечен как отменённый и не имеет ни кода ответа, ни заголовков. В консоли — сообщение о блокировке клиентом, а не о политике общего доступа.

Как проверить. Откройте страницу в приватном окне без расширений или в другом профиле браузера. Если запросы появились — дело в расширении. Дополнительно сравните две записи в сетевой панели: у отказа сервера всегда есть код ответа, у блокировки клиентом кода нет.

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

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

Что проверить

  1. Адрес приёма в теге или в инициализации совпадает с адресом на экране установки трекера, и это не адрес кабинета.
  2. Предварительный запрос OPTIONS на /v1/track отвечает 204, а в Access-Control-Allow-Headers есть все заголовки, которые вы отправляете.
  3. Своих заголовков к запросам приёма нет.
  4. Заголовок запроса Origin присутствует в списке origin токена — буква в букву, включая схему и порт.
  5. В консоли нет сообщений про Content-Security-Policy, а worker-src проверен отдельно от остальных директив.
  6. Проверка выполнена в окне без расширений — иначе половина выводов будет о блокировщике, а не об интеграции.