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

Не тот ключ или не тот origin

Семь причин, по которым приём отвечает 401 или 403 — перепутанные ключи, чужой проект, устаревший префикс, отзыв, origin и список событий, — и как найти причину без подсказки в ответе.

Кому
Разработчикам, получающим 401 или 403 на приёме
Проверено
На этой странице

Приём событий отвечает 401 и 403 намеренно скупо: одно и то же сообщение для разных причин, никакого эха присланного значения и никакой подсказки, что именно не совпало. Это защита от подбора ключей — и одновременно то, что делает диагностику неочевидной.

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

Быстрое различение

Что видно Причина Разбор
Запросов нет вовсе, консоль молчит серверный ключ в теге серверный ключ в браузере
403 credential_origin_forbidden с бэкенда браузерный токен на сервере браузерный токен на бэкенде
202, событий нет ни на одном экране ключ другого проекта ключ из другого проекта
Всё работает, но в ответе Deprecation: true устаревший префикс устаревший префикс
401 unauthorized внезапно, без изменений в коде отзыв или истечение срока ключ отозван или истёк
403 credential_origin_forbidden из браузера origin не в списке origin не в списке
202 с credential_event_forbidden имя события не разрешено ключу событие вне области действия

Перепутанные ключи

Серверный ключ попал в браузер

Вероятная причина. В тег или в вызов инициализации подставлено значение с префиксом pp_sk_. Трекер такое значение не принимает: он проверяет форму credential до запуска и допускает только pp_bt_ и pp_wk_. В режиме тега автозапуск молча ничего не делает — ошибки в консоли нет. В сборке из пакета инициализация бросает исключение.

Как проверить. В сетевой панели нет ни запроса /v1/cfg, ни запросов на /v1/track, а window.pp при этом определён. Найдите префикс серверного ключа в отданной браузеру разметке и в бандле:

grep -rn "pp_sk_" dist/ public/

Проверьте и контейнер тег-менеджера — в него значение попадает мимо репозитория.

Безопасное исправление. Выпустите браузерный токен с нужным списком origin и подставьте его. Серверный ключ верните на бэкенд — в переменную окружения. Затем отзовите утёкший ключ: он уже был виден каждому посетителю страницы, и «переставить его обратно» недостаточно.

Если не помогло. Проверьте, не указаны ли в теге сразу data-token и data-key с разными значениями: в этом случае автозапуск тоже молча отказывается работать. Оставьте один атрибут.

Браузерный токен используется на бэкенде

Вероятная причина. У запроса с сервера нет заголовка Origin, а браузерный токен обязан пройти проверку точного origin. Пустой или непрозрачный origin не совпадает ни с чем, поэтому запрос получает 403 credential_origin_forbidden — на /v1/track и /v1/identify. На /v1/alias ответ будет другим: 403 credential_scope_forbidden, потому что браузерному токену связывание идентификаторов недоступно вообще.

Как проверить. Посмотрите префикс типа в значении, которое читает бэкенд:

printf '%s\n' "${ACTIONPULSE_SERVER_KEY:0:6}"

Ожидаемо pp_sk_. Если там pp_bt_ — причина найдена. Второй признак: тот же ключ из браузера работает, а с сервера — нет.

Безопасное исправление. Выпустите серверный ключ и переведите бэкенд на него. Не пытайтесь «починить» это подстановкой заголовка Origin в серверный запрос: проверка сработает, но вы получите публичный ключ в роли секретного, с чужими ограничениями и сроком.

Если не помогло. Убедитесь, что бэкенд не отправляет подтверждённые бизнес-факты браузерным токеном: payment_succeeded, order_paid, registered и подобные принимаются только с серверным ключом, и отказ по ним приходит поэлементно внутри 202, а не как 403.

Ключ из другого проекта

Вероятная причина. Ключ действителен, но принадлежит другому проекту той же организации — обычно после копирования сниппета со стенда или из другого окружения. Отказа не будет: ответ 202, события приняты — просто в чужой проект. Проект определяется ключом, а не чем-либо в запросе.

Как проверить. Возьмите префикс ключа — 8 символов между вторым и третьим подчёркиванием — и поищите его в списке «Credentials проекта» настроек проекта. Если префикса там нет, откройте тот же список в других проектах: он найдётся там. Второй признак — события видны на экране проверки интеграции чужого проекта.

Безопасное исправление. Замените значение на ключ нужного проекта и перепроверьте, что события появились там, где ожидаются. Ключ чужого проекта отзывать не нужно — он не скомпрометирован, он просто не тот.

Если не помогло. События, уже принятые чужим проектом, остаются там: переносить события между проектами ActionPulse не умеет. Доступные варианты — удаление данных субъекта по идентификатору пользователя (роль администратора, см. приватность) либо ожидание срока хранения. Если чужой проект был создан только для теста, его можно удалить целиком вместе с данными.

Ключ больше не тот, что нужен

Устаревший префикс ключа

Вероятная причина. Используется ключ с префиксом pp_wk_ — такие выпускались до разделения типов. Они продолжают работать, но несут старый широкий профиль: и публичные, и секретные права сразу, без привязки к origin и без срока. Признак в ответе — заголовок Deprecation: true, но только когда ключ передан в адресе запроса.

Как проверить. Посмотрите тип в списке credentials проекта — устаревшие ключи помечены отдельно. В коде проверьте, не уходит ли ключ в адресе:

grep -rn "?key=" dist/ public/ src/

Безопасное исправление. Порядок, который не теряет события:

  1. выпустите браузерный токен со всеми origin и всеми своими именами событий, которые отправляются из браузера;
  2. замените значение в теге на новый токен, атрибут data-key уберите;
  3. выпустите серверный ключ и переведите бэкенд на него;
  4. убедитесь, что события идут с обеих сторон;
  5. отзовите устаревший ключ.

Если не помогло. Ротация устаревшего ключа сохраняет его тип: получится новое устаревшее значение, а не браузерный или серверный ключ. Для перехода нужен именно выпуск ключа нужного типа.

Ключ отозван или истёк

Вероятная причина. 401 с кодом unauthorized и сообщением invalid credential приходит и на отозванный, и на истёкший ключ — сообщение одинаковое. Браузерный токен обязан иметь срок, по умолчанию это 90 дней с выпуска, поэтому «внезапный 401 без изменений в коде» чаще всего означает именно истечение.

Как проверить. В списке credentials проекта посмотрите столбцы «Истекает» и «Статус» у нужного префикса. Проверить сам факт приёма ключа можно безопасным запросом: авторизация выполняется до разбора тела, поэтому пустой пакет ничего не запишет.

curl -sS -o /dev/null -w '%{http_code}\n' \
  -X POST "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Authorization: Bearer $ACTIONPULSE_SERVER_KEY" \
  -d '{"batch":[]}'

422 — ключ принят; 401 — нет.

Безопасное исправление. Истёкший токен ротировать нельзя — выпустите новый и замените значение. Для ключа, который вы отозвали случайно, восстановления тоже нет: нужен новый ключ. Ротация помогает только в одном случае — когда значение попало в чужие руки, а профиль ключа менять не нужно.

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

Ограничения браузерного токена

Origin не в списке разрешённых

Вероятная причина. Браузерный токен проверяет заголовок Origin на точное совпадение со списком, заданным при выпуске. Маски не поддерживаются; схема, поддомен и непустой порт входят в origin; пустой список означает отказ всегда. Ответ — 403 credential_origin_forbidden.

Регистр, завершающий слеш, завершающая точка в имени хоста и порт по умолчанию (80 для http, 443 для https) приводятся к одному виду и на результат не влияют.

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

curl -sS -o /dev/null -w '%{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 после выпуска не редактируется — ни правкой, ни ротацией. Выпустите новый токен, перечислив все адреса сразу (от 1 до 32): production, варианты с www и без, поддомены со своими страницами, адрес предпросмотра сборок. Замените значение, проверьте приём и затем отзовите старый токен.

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

Событие вне разрешённой области действия

Вероятная причина. Ключ принят, но конкретное имя события ему не разрешено. Отказ приходит поэлементно внутри 202:

{ "accepted": 0, "n": 1, "rejected": [{ "i": 0, "code": "credential_event_forbidden" }] }

Причин у этого кода четыре:

Ситуация Что делать
Своё имя события не входит в список токена выпустить токен с нужным списком или отправлять с сервера
Подтверждённый бизнес-факт отправляется из браузера отправлять серверным ключом
Имя из пространства pp.* отправляется серверным ключом это пространство формирует браузерный сбор
В запросе заявлен внутренний источник убрать X-PP-Source: internal и context.source, иначе отклоняется каждый элемент пакета

Только что выпущенный браузерный токен разрешает ровно одно своё имя — signup_completed. Список задаётся при выпуске: от 1 до 128 имён, каждое по маске ^[a-z0-9_.]{1,128}$, и ни одно не может начинаться с pp.. Встроенные события pp.* списком не ограничены и работают независимо от него.

Как проверить. Сверьте имя события из rejected со списком имён у токена и с реестром событий проекта. На экране проверки интеграции отказ виден вместе с именем события, что быстрее, чем читать ответы вручную.

Безопасное исправление. Для сигналов интерфейса — выпустите токен с полным списком своих имён. Для подтверждённых фактов — переносите отправку на бэкенд: это не настройка, а граница доверия, и обойти её списком нельзя.

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

Почему в ответе не видно, что не совпало

Это сделано намеренно, и знать про это полезно, чтобы не искать подсказку там, где её нет:

  • сообщение при 401 одинаково для всех причин — иначе по ответам можно было бы перебором отделять существующие ключи от несуществующих, а отозванные от истёкших;
  • ни один ответ не возвращает присланное значение ключа и не эхом повторяет присланный origin — иначе ключ утекал бы в логи, трассировки и отчёты об ошибках;
  • публичная конфигурация сбора отвечает 200 и заведомо безопасным «всё выключено» для неизвестного, отозванного, истёкшего и не подходящего по типу префикса — существование ключа не раскрывается;
  • поэлементный отказ несёт только индекс и код, без объяснения, какое именно правило сработало.

Как диагностировать без подсказки

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

Префикс — это идентификатор. 8 символов между вторым и третьим подчёркиванием не секретны: по ним ключ находят в списке проекта, их можно писать в тикет и в логи. Секрет — только остаток значения.

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

Отметка «Использован» Вывод
Обновляется, а запросы получают 403 значение ключа верное, не совпадает политика: тип, область действия или origin
Не обновляется, запросы получают 401 до проверки политики дело не дошло: значение не то, отозвано или истекло
Пусто, ключ выпущен давно этим ключом не пользовались ни разу — вы смотрите не на тот ключ

Отметка отстаёт до минуты, поэтому сравнивайте после небольшой паузы, а не мгновенно.

Проверка конфигурации по префиксу. Для браузерного и устаревшего ключа конфигурацию сбора можно запросить публично, передав только префикс — 8 символов, а не всё значение:

curl -sS "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/cfg?key=YOURPREF" \
  -H "Origin: https://example.com"

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

Пустой пакет как проба. Запрос с телом {"batch":[]} проходит авторизацию, но ничего не записывает: 422 означает «ключ и политика в порядке», а любой другой код указывает слой, на котором отказ произошёл. Это самая безопасная проба на продакшене.

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

  1. В разметке и в бандле нет префикса серверного ключа — проверены и репозиторий, и контейнер тег-менеджера.
  2. Бэкенд читает ключ с префиксом pp_sk_ из переменной окружения, а не браузерный токен из конфига фронтенда.
  3. Префикс из тега и префикс из бэкенда оба присутствуют в списке credentials нужного проекта.
  4. У браузерного токена в списке origin есть все рабочие адреса, а в календаре есть напоминание о его сроке.
  5. Список своих имён событий у токена совпадает с тем, что действительно вызывает фронтенд.
  6. В логи вашего приложения попадают код ошибки и префикс, но никогда само значение ключа.