Настройка webhooks

OSA Proxy отправляет HTTP POST-уведомления о скачивании и блокировке файлов. Webhook не участвует в обработке запроса клиента: загрузка не ждёт receiver, а ошибка доставки не изменяет ответ OSA Proxy.

Уведомления доступны для всех поддерживаемых экосистем, кроме Docker. Они создаются только для GET-запросов к маршрутам репозиториев. HEAD-запросы, служебные endpoints и обычные ошибки upstream уведомлений не создают.

События

СобытиеКогда отправляется
file_downloadedOSA Proxy вернул файл со статусом 200 или 206 без завершённой проверки CodeScoring. Подписка на это событие охватывает все успешные скачивания, включая проверенные. Для проверенного скачивания в уведомлении будет указан trigger scanned_file_downloaded.
scanned_file_downloadedOSA Proxy вернул файл со статусом 200 или 206 после завершённой проверки CodeScoring, в том числе по вердикту из кэша.
file_blockedOSA Proxy явно заблокировал запрос. Поле scanned показывает, был ли к этому моменту получен завершённый вердикт CodeScoring.

Если подписка содержит одновременно file_downloaded и scanned_file_downloaded, для одного проверенного запроса отправляется только одно уведомление с trigger: scanned_file_downloaded.

Статус 200 или 206 означает ответ, зафиксированный OSA Proxy. Он не подтверждает, что клиент полностью получил или сохранил файл.

Формат запроса

Каждый POST содержит ровно одно событие:

{
  "events": [
    {
      "created_at": "2026-09-10T10:15:30.123456Z",
      "trigger": "scanned_file_downloaded",
      "payload": {
        "scanned": true,
        "reason": "codescoring evaluation completed",
        "repository": "company-npm",
        "ecosystem": "npm",
        "object_kind": "package",
        "path": "/company-npm/lodash/-/lodash-4.17.21.tgz",
        "purl": "pkg:npm/lodash@4.17.21"
      }
    }
  ]
}

Поля payload:

ПолеОписание
scannedtrue, если CodeScoring evaluation завершилась.
reasonПричина результата: завершённая проверка, блокировка, отключённая или пропущенная проверка и т. п. Значение предназначено для диагностики и может расширяться.
repositoryПубличное имя маршрута репозитория. Именно с ним сравнивается фильтр repositories.
ecosystemЭкосистема пакета, например npm, maven или pypi.
object_kindИзвестный OSA Proxy тип объекта: file, package или manifest.
pathCanonical escaped path исходного запроса без query string.
purlPURL пакета, если OSA Proxy смог его определить. В остальных случаях поле отсутствует.

OSA Proxy отправляет Content-Type: application/json. При настроенном секрете также отправляется X-CodeScoring-Authentication. Подписи запроса отдельным заголовком нет.

Webhook продолжает trace исходного запроса: receiver получает trace-заголовки и тот же X-Trace-Id, который используется для upstream и CodeScoring-запросов.

Тестовый POST отличается от рабочего уведомления и проверяет только соединение:

{
  "events": [
    {
      "created_at": "2026-09-10T10:15:30.123456Z",
      "trigger": "test",
      "payload": {}
    }
  ]
}

Receiver может вернуть любой статус 2xx: OSA Proxy считает такую доставку успешной и игнорирует тело ответа.

Настройка через osa-proxy.yml

В режиме без Configuration Store добавьте подписки в корень osa-proxy.yml. В token_env укажите имя переменной окружения с токеном:

webhooks:
  - name: downloads
    url: https://hooks.example.com/osa-proxy/events
    token_env: WEBHOOK_DOWNLOADS_TOKEN
    enabled: true
    events:
      - file_downloaded
      - scanned_file_downloaded
      - file_blocked
    repositories:
      - company-npm
      - company-maven
    timeout: 3s
    buffer_size: 256

После изменения YAML перезапустите все реплики OSA Proxy. Для одинакового поведения каждая реплика должна получить одинаковый список подписок и токенов.

Параметры подписки

ПараметрОбязательность и поведение
nameЧитаемое имя. В managed mode обязательно.
urlАбсолютный http или https URL receiver. Credentials в URL managed mode не принимает.
token_envНеобязательное имя переменной окружения с токеном receiver. Токен передаётся в X-CodeScoring-Authentication, но не хранится в конфигурации.
enabledВключает подписку. В YAML при отсутствии имеет значение true; в managed API поле обязательно.
eventsНепустой список поддерживаемых событий.
repositoriesТочный, регистрозависимый список публичных имён репозиториев. Пустой или отсутствующий список принимает события всех non-Docker репозиториев.
timeoutПолный timeout одного POST. По умолчанию 3s; в managed mode допустимо значение от 1ns до 30s.
buffer_sizeРазмер отдельной очереди подписки. По умолчанию 256; в managed mode допустимо от 1 до 4096.

Каждая включённая подписка имеет собственную ограниченную очередь и один последовательный sender. Если очередь заполнена, новое уведомление удаляется. OSA Proxy раз в 30 секунд пишет суммарное количество таких потерь в warning-лог webhook notifications dropped.

На одно уведомление выполняется только одна попытка. Retry и сохранения на диск нет. Redirect не выполняется: ответ 3xx считается ошибкой доставки. Ответы не 2xx, timeout и сетевые ошибки записываются в warning-лог webhook delivery failed, но не влияют на клиентский запрос.

Доставка best effort

При переполнении очереди, остановке процесса или ошибке receiver уведомление может быть потеряно. Не используйте webhook как единственный источник аудита или как транзакционное подтверждение скачивания.

Аутентификация receiver

Создайте Secret с токеном receiver и передайте его в OSA Proxy через переменную окружения. Затем укажите имя переменной в token_env:

webhooks:
  - name: downloads
    url: https://hooks.example.com/osa-proxy/events
    token_env: WEBHOOK_DOWNLOADS_TOKEN

Не записывайте значение токена в конфигурацию или репозиторий. В managed mode передайте token_env через Admin API вместе с подпиской — добавлять тот же webhook в osa-proxy.yml не нужно. API хранит и возвращает только имя переменной, но не токен.

Если переменная не задана или пуста, подписка не активируется. После добавления или изменения Secret перезапустите все реплики OSA Proxy.

Проверка подключения через Admin API

Admin API работает на отдельном listener, по умолчанию 127.0.0.1:8081, и использует Bearer Admin Token. В Kubernetes не публикуйте admin Service наружу без TLS, аутентифицированного ingress/reverse proxy и сетевых ограничений.

Проверить ещё не сохранённую подписку можно независимо от того, включён Configuration Store или нет:

curl --fail-with-body \
  -H "Authorization: Bearer ${OSA_PROXY_ADMIN_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "qa-receiver",
    "url": "https://hooks.example.com/osa-proxy/events",
    "token_env": "WEBHOOK_DOWNLOADS_TOKEN",
    "enabled": true,
    "events": ["file_blocked"],
    "repositories": [],
    "timeout": "3s",
    "buffer_size": 16
  }' \
  http://127.0.0.1:8081/api/v1/configuration/webhooks/test

Пример успешного результата:

{"success":true,"status_code":204,"duration_ms":18}

success: true означает только то, что тестовый POST получил 2xx. Возможные error_category: invalid_configuration, timeout, connection, http_status.

Управление подписками через Admin API

CRUD подписок доступен только при включённом Configuration Store. При выключенном хранилище эти endpoints возвращают 503 managed_storage_disabled; используйте YAML. Swagger UI доступен на /swagger/, OpenAPI — на /openapi.json admin listener.

Основные endpoints:

Метод и pathНазначение
GET /api/v1/configuration/webhooks/triggersСписок поддерживаемых событий.
GET /api/v1/configuration/webhooksВсе подписки и текущий section ETag.
PUT /api/v1/configuration/webhooksПолностью заменить список подписок. В элементах требуется id.
POST /api/v1/configuration/webhooksСоздать подписку. id передавать нельзя: сервер создаёт его сам.
GET /api/v1/configuration/webhooks/{id}Получить одну подписку.
PUT /api/v1/configuration/webhooks/{id}Полностью заменить одну подписку.
PATCH /api/v1/configuration/webhooks/{id}Изменить одно или несколько полей. null, пустой объект и неизвестные поля запрещены.
DELETE /api/v1/configuration/webhooks/{id}Удалить подписку.
POST /api/v1/configuration/webhooks/testОтправить тест для подписки из тела запроса без сохранения.
POST /api/v1/configuration/webhooks/{id}/testПроверить сохранённую подписку. Тело запроса не требуется.

Все изменяющие запросы используют optimistic concurrency. Сначала получите ETag всей секции, затем передайте его без изменений в If-Match:

curl -i \
  -H "Authorization: Bearer ${OSA_PROXY_ADMIN_TOKEN}" \
  http://127.0.0.1:8081/api/v1/configuration/webhooks

Скопируйте значение заголовка ETag, включая двойные кавычки:

curl --fail-with-body \
  -H "Authorization: Bearer ${OSA_PROXY_ADMIN_TOKEN}" \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "section-webhooks-1:sha256:REPLACE_WITH_ACTUAL_HASH"' \
  -d '{
    "name": "blocked-downloads",
    "url": "https://hooks.example.com/osa-proxy/events",
    "token_env": "WEBHOOK_DOWNLOADS_TOKEN",
    "enabled": true,
    "events": ["file_blocked"],
    "repositories": ["company-npm"],
    "timeout": "3s",
    "buffer_size": 256
  }' \
  http://127.0.0.1:8081/api/v1/configuration/webhooks

Успешная mutation возвращает новый ETag. Использование старого ETag возвращает 409 section_conflict; повторите GET, проверьте актуальное состояние и осознанно повторите изменение. Все поля request body проверяются строго, а число подписок ограничено 100.

Проверка сохранённой подписки не создаёт новую ревизию:

curl --fail-with-body -X POST \
  -H "Authorization: Bearer ${OSA_PROXY_ADMIN_TOKEN}" \
  http://127.0.0.1:8081/api/v1/configuration/webhooks/WEBHOOK_ID/test

Ответ содержит section_etag, по которому клиент может определить, не изменилась ли секция с момента чтения.

Диагностика

Проверьте следующие сообщения структурированных логов:

  • webhook delivered на уровне debug — receiver вернул 2xx;
  • webhook delivery failed на уровне warning — сеть, timeout или ответ не 2xx;
  • webhook notifications dropped на уровне warning — очередь подписки переполнена; поле count содержит число потерь за интервал.

Если test endpoint успешен, а рабочее событие отсутствует, проверьте method запроса, экосистему, публичное имя repository, enabled, список events, HTTP status ответа OSA Proxy и факт классификации запроса как файла или пакета.

Страница была полезна?