Yandex Metrika

Webhooks & Replay

Описание

Общее описание функционала

Повтор запроса (Replay) позволяет безопасно отправить заново входящий HTTP-запрос, который был зафиксирован инспектором трафика на вашем туннеле — например, вызов вебхука от внешнего сервиса. Запрос уходит только на настроенную цель этого туннеля; произвольные внешние адреса недоступны.

Как пользователь может использовать

  • Разработчик получил вебхук с ошибкой 500, находит событие в инспекторе и повторяет запрос на локальный сервис после исправления кода.
  • Пользователь меняет путь или добавляет тестовое тело запроса, чтобы воспроизвести сценарий без повторного вызова извне.
  • Пользователь сравнивает код ответа и время ответа после повтора с исходным событием.
  • Владелец туннеля проверяет обработчик вебхука на staging-окружении через туннель.

Как это реализовано в сервисе

Сервис восстанавливает метод, путь и безопасные заголовки из сохранённого события, применяет ваши правки и отправляет запрос через тот же туннель на его целевой адрес. Если на тарифе включён захват тел запросов и тело запроса сохранено целиком, оно подставляется автоматически; иначе тело можно указать вручную в форме повтора или в overrides.body. Обрезанное сохранённое тело не отправляется без явной подстановки. Учётные данные, cookies и служебные заголовки удаляются. Результат повтора (код ответа и длительность) возвращается сразу в интерфейсе.

Справочник API

API

Повтор запроса

POST /api/v1/inspector/events/{eventId}/replay

Требуется авторизация и доступ к туннелю события. Доступна на тарифах с функцией «Webhooks & Replay».

Тело запроса

| Поле | Описание |

| --- | --- |

| mode | Необязательно, устарело. Значения cloud и local эквивалентны — запрос идёт на цель туннеля. |

| overrides.method | Необязательный HTTP-метод. |

| overrides.path | Необязательный путь и query (должен начинаться с /). Заменяет путь и query из события. |

| overrides.headers | Необязательные дополнительные заголовки (без cookies, Authorization, Host и др.). |

| overrides.body | Необязательное тело запроса (до 32 КБ). Если не указано и для события сохранено полное тело запроса (захват тел на тарифе), используется сохранённое значение. При обрезанном сохранённом теле поле обязательно. |

Успешный ответ 200

| Поле | Описание |

| --- | --- |

| event_id | Идентификатор исходного события. |

| downstream_status | HTTP-код ответа целевого сервиса. |

| duration_ms | Время доставки в миллисекундах. |

| delivered | true, если ответ от цели получен. |

Подробнее — в разделе ограничения повтора вебхука.

Ошибки

Ошибки

| Ситуация | HTTP | Сообщение (пример) |

| --- | --- | --- |

| Нет входа | 401 | unauthorized |

| Нет доступа к туннелю / функции | 403 | forbidden |

| Событие не найдено | 404 | event not found |

| Событие не пригодно для повтора | 400 | event is not replayable |

| Сохранённое тело запроса обрезано, а overrides.body не указано | 400 | stored request body was truncated; provide a body override to replay |

| Событие обрезано политикой захвата (уровень метаданных) | 400 | cannot replay truncated event |

| Недопустимый путь или заголовок в overrides | 400 | описание валидации |

| Туннель не активен или клиент не подключён | 400 / 503 | tunnel is not active / tunnel client is not connected |

| Превышен лимит запросов | 429 | rate limit exceeded |

| Цель недоступна (таймаут, отказ) | 502 / 504 | понятная подсказка шлюза |

| Внутренняя ошибка | 500 | internal server error |

Ответ 4xx или 5xx от целевого сервиса при успешной доставке возвращается в поле downstream_status с кодом 200 у API повтора.

Ограничения

Известные ограничения

  • Повторяются только HTTP/HTTPS-события, помеченные как пригодные для повтора.
  • Тело запроса при повторе: если на тарифе включён захват тел и тело сохранено без обрезки — подставляется автоматически; если тело не сохранялось (только метаданные) — нужно указать overrides.body или поле в форме повтора; если тело было обрезано лимитом захвата — автоматический повтор блокируется до явной подстановки тела.
  • Назначение фиксировано: целевой адрес туннеля из события, без выбора другого хоста.
  • Заголовки авторизации, cookies, Host и hop-by-hop не передаются и не могут быть добавлены через overrides.
  • Для повтора клиент туннеля должен быть подключён; иначе доставка невозможна.
  • Действует лимит частоты запросов и ограничение по времени ожидания ответа цели.

Дополнения

Compare attempts & bulk / alternate replay

Общее описание функционала

Сравнение и массовый повтор дополняют базовый Replay в инспекторе трафика.

Пользователь может сопоставить два сохранённых события и увидеть различия в методе, адресе, заголовках, теле, статусе и длительности — без раскрытия секретов в теле, query или заголовках. Можно повторить выбранную группу событий одной операцией и отправить запросы на текущую цель туннеля, на порт этой же цели или на заранее разрешённый HTTPS-адрес облачного inbox. Для каждой попытки сервис сохраняет отдельный результат в инспекторе и связывает его с исходным событием.

Одиночный повтор через форму события остаётся совместимым: в ответе event_id всегда указывает на исходное событие, а поля попытки (attempt_event_id, attempt_number, root_event_id) добавляются рядом.

Как пользователь может использовать

  • Откройте туннель → Инспектор → События, выберите два события и нажмите Сравнить, чтобы проверить, что изменилось между двумя вызовами вебхука.
  • В диалоге сравнения читайте пометки у тела (missing, invalid_json, text, truncated, unavailable, masked_full, available) — они описывают доступность данных, а не ошибку API.
  • Выберите несколько событий и запустите Массовый повтор, если нужно прогнать один и тот же обработчик на всей выборке.
  • Задайте режим Только ошибочные или временной диапазон, чтобы повторить подходящую группу без ручного отбора.
  • Оставьте назначение Текущая цель туннеля, если запрос должен идти на адрес, настроенный для туннеля.
  • Откройте Другой адрес и укажите Порт цели, если нужен другой порт на той же принадлежащей вам цели (хост и схема задаются туннелем, не вводятся вручную).
  • Выберите Удалённый HTTPS, если запрос должен попасть на разрешённый HTTPS-адрес под самым длинным префиксом inbox, который принадлежит вам; query, учётные данные и fragment в адресе не допускаются.
  • Следите за счётчиками в карточке задания; после завершения откройте ссылку на попытку, чтобы увидеть статус и вернуться к исходному событию.
  • Нажмите Отменить: ещё не отправленные элементы станут cancelled; для запроса, который уже ушёл, но итог не подтверждён, результат будет unknown; завершённые попытки не отзываются.
  • Используйте стабильный заголовок X-Fortunnels-Replay-ID на стороне получателя, если нужна дедупликация при повторной доставке.

Массовый повтор работает по модели «не менее одного раза»: после сбоя процесса один и тот же запрос может быть доставлен повторно с тем же идентификатором повтора. Сервис не обещает доставку «ровно один раз».

Как это реализовано в сервисе

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

При сравнении сервис возвращает только защищённые представления тел и очищенные заголовки. Секреты из тела, query и чувствительных заголовков не попадают в ответ сравнения. Перед отправкой на альтернативный адрес проверяются тарифные возможности, принадлежность адреса и правила безопасного соединения; при отказе сервис не раскрывает, существует ли чужой объект. Задания можно отменять; незавершённые элементы возвращаются в работу после истечения срока ожидания.

Сравнение событий

POST /api/webhook-replay/compare

Сравнивает два события, доступных текущему пользователю. Требуется авторизация, функция «Webhooks & Replay» с дополнением сравнения и массового повтора, CSRF-токен для запросов из браузера.

Тело запроса:

{
  "left_event_id": "<uuid>",
  "right_event_id": "<uuid>"
}

Успешный ответ 200:

| Поле | Описание |

| --- | --- |

| left_event_id, right_event_id | Сравниваемые события |

| truncated | true, если хотя бы одна сторона ограничена по размеру при сравнении |

| diffs | Список различий по полям |

Элемент diffs:

| Поле | Описание |

| --- | --- |

| field | Имя поля (method, path, query, header:…, request_body, response_status, response_body, duration_ms и др.) |

| left, right | Защищённые значения сторон (маскированные для тел и чувствительных заголовков) |

| equal | Совпадают ли значения |

| kind | scalar, header, json или text |

| left_note, right_note | Пометка доступности тела на каждой стороне (см. ниже) |

Пометки тела (left_note / right_note) — данные ответа, не HTTP-ошибка:

| Пометка | Смысл |

| --- | --- |

| available | Тело доступно для сравнения |

| missing | Тело не было сохранено |

| unavailable | Тело не удалось прочитать из хранилища |

| masked_full | Тело доступно только в защищённом виде |

| truncated | Сохранённое или сравниваемое тело обрезано по лимиту |

| invalid_json | Тело не является валидным JSON |

| text | Тело сравняется как текст, не как JSON |

Секреты из тела, query и чувствительных заголовков не возвращаются в ответе сравнения.

Одиночный повтор (совместимость)

POST /api/v1/inspector/events/{eventId}/replay

Поведение базового Replay сохранено. В успешном ответе 200 поле event_id всегда указывает на исходное событие. Дополнительные поля попытки:

| Поле | Описание |

| --- | --- |

| attempt_event_id | Идентификатор нового события попытки в инспекторе |

| attempt_number | Порядковый номер попытки для корневого события |

| root_event_id | Идентификатор корневого (исходного) события |

Полный контракт одиночного повтора — в документации Webhooks & Replay.

Массовый повтор

POST /api/webhook-replay/jobs

Создаёт задание массового повтора. Требуется авторизация, доступ к туннелю и функция «Webhooks & Replay» с дополнением массового повтора. Для запросов из браузера также требуется CSRF-токен.

Общие поля:

| Поле | Описание |

| --- | --- |

| tunnel_id | Идентификатор туннеля пользователя |

| selector_mode | selected, failed_only или time_range |

| event_ids | Список событий для режима selected |

| from, to | Границы времени в формате RFC 3339 для режима time_range |

| destination_kind | current, local_port или remote_https |

| local_port | Порт от 1 до 65535 для local_port (только порт; хост и схема берутся из цели туннеля) |

| remote_url | HTTPS-адрес без query, учётных данных и fragment для remote_https; путь должен находиться под самым длинным префиксом inbox, принадлежащим вызывающему |

Назначения:

| destination_kind | Поведение |

| --- | --- |

| current | Целевой адрес туннеля, как при обычном Replay |

| local_port | Тот же хост и схема, что у цели туннеля, но указанный порт; произвольный хост не допускается |

| remote_https | Разрешённый HTTPS-адрес облачного inbox; перенаправления не выполняются; для тарифа нужен доступ к удалённой пересылке |

За одно задание принимается не более 500 событий. Для одного пользователя одновременно может быть не более пяти активных заданий.

Ответ 200 содержит объект job с идентификатором, состоянием, режимом выбора, назначением, счётчиками total_count, succeeded_count, failed_count, skipped_count, cancelled_count, unknown_count и пояснением о доставке «не менее одного раза`.

Состояние и отмена

GET /api/webhook-replay/jobs/{jobId}

Возвращает состояние и счётчики задания. Терминальные состояния задания: completed, completed_with_errors, cancelled.

POST /api/webhook-replay/jobs/{jobId}/cancel

Запрашивает отмену задания. Элементы, которые ещё не отправлены, переходят в cancelled. Запрос, который уже ушёл, но итог доставки не подтверждён, обозначается как unknown. Завершённые попытки сохраняются.

Список заданий

GET /api/webhook-replay/jobs?tunnel_id=<tunnelId>

Возвращает задания указанного туннеля текущего пользователя.

Каждая завершённая попытка доступна через ссылку в инспекторе и содержит стабильный идентификатор X-Fortunnels-Replay-ID для дедупликации на стороне получателя.

Ошибки сравнения и массового повтора

| Ситуация | HTTP | Что сделать |

| --- | --- | --- |

| Не выполнен вход | 401 | Войдите в аккаунт и повторите запрос. |

| Функция недоступна по тарифу или аккаунт приостановлен | 403 | Проверьте тариф и состояние аккаунта. Для удалённой HTTPS-цели включите доступ к удалённой пересылке inbox. |

| Событие, туннель, задание или удалённый адрес недоступны текущему пользователю | 404 | Проверьте идентификатор и права доступа. Сервис намеренно не раскрывает существование чужих объектов — ответ не отличается от «не найдено». |

| Неверный режим выбора, UUID, порт или адрес назначения | 400 | Исправьте поля запроса. Для local_port передавайте только порт. Для remote_https используйте разрешённый HTTPS-адрес без query, учётных данных и fragment, под вашим префиксом inbox. |

| Сохранённое тело обрезано | 400 | Укажите новое тело явно в форме повтора или в overrides.body. |

| Слишком много событий или активных заданий | 400 или 409 | Уменьшите выборку (лимит 500 событий на задание, 5 активных заданий на пользователя), дождитесь завершения активного задания и повторите операцию. |

| Превышен лимит запросов | 429 | Подождите и повторите позже. |

| Цель не отвечает или отклонила соединение | 502 или 504 | Проверьте доступность цели туннеля или удалённого inbox. Запись о неудачной попытке останется в задании. |

| Запрос отменён во время доставки | 200 | Откройте результат элемента: он может быть cancelled или unknown, если итог доставки нельзя подтвердить. |

| Внутренняя ошибка сервиса | 500 | Повторите операцию. Если ошибка сохраняется, обратитесь в поддержку. |

HTTP-ответ целевого сервиса не означает ошибку API повтора: код цели записывается в результате попытки и доступен в инспекторе.

Пометки тела при сравнении (не ошибки)

Поля left_note и right_note в ответе POST /api/webhook-replay/compare описывают доступность тела на каждой стороне. Они не сопоставляются с HTTP-кодами ошибки.

| Пометка | Когда появляется | Что ожидать |

| --- | --- | --- |

| missing | Тело не было сохранено для события | Сравнение по телу ограничено; секреты не раскрываются |

| unavailable | Тело не удалось прочитать | Повторите сравнение позже; при постоянной ошибке обратитесь в поддержку |

| masked_full | Доступно только защищённое представление | Различия видны в маскированном виде |

| truncated | Тело обрезано по лимиту сравнения | Полное содержимое может быть недоступно в сравнении |

| invalid_json | Тело не является валидным JSON | Сравнение выполняется как текст, не как структурированный JSON |

| text | Тело сравняется как текст | Нормальный режим для не-JSON содержимого |

| available | Тело доступно для сравнения | Обычный случай при сохранённом теле |

Известные ограничения

  • За одно массовое задание можно выбрать не более 500 событий; одновременно у пользователя может быть не более пяти активных заданий.
  • Выборки failed_only и time_range фиксируются при создании задания. Новые события не добавляются в уже созданное задание.
  • Сервис доставляет запросы не менее одного раза. При сбое после отправки возможна повторная доставка с тем же X-Fortunnels-Replay-ID. Доставка «ровно один раз» не гарантируется.
  • Событие с обрезанным телом нельзя повторить автоматически без явного нового тела.
  • Сравнение ограничивает размер каждой стороны. Большие тела показываются с пометкой truncated.
  • Пометки тела при сравнении (missing, unavailable, masked_full, truncated, invalid_json, text, available) различаются и не объединяются в одну категорию.
  • Секреты из тела, query и чувствительных заголовков не предназначены для просмотра через сравнение; в ответе только защищённые представления.
  • Для local_port хост и схема берутся из принадлежащей пользователю цели туннеля; указать произвольный хост нельзя — только порт.
  • Удалённая цель (remote_https) должна быть разрешена для пользователя, использовать HTTPS, находиться под самым длинным принадлежащим префиксом inbox и пройти проверки безопасного соединения. Query, учётные данные и fragment в адресе не допускаются. Перенаправления не выполняются.
  • При отказе по правам или принадлежности удалённого адреса сервис отвечает как при «не найдено», без раскрытия существования чужого объекта.
  • Отмена не отзывает запрос, который уже был отправлен. Его итог сохраняется как доставленный, ошибочный или unknown, если результат доставки не подтверждён. Ещё не отправленные элементы переходят в cancelled.
  • Одиночный повтор сохраняет совместимость: event_id в ответе всегда указывает на исходное событие; поля попытки (attempt_event_id, attempt_number, root_event_id) добавляются отдельно.