env-master
ENСоздать аккаунт
Разделы документации

Вебхуки

Сервер сам сообщает вашей системе об изменениях.

Зачем

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

Вебхук нужен, когда реагировать надо сразу: перечитать конфиг без перезапуска, поднять тревогу при неудачной ротации, узнать, что одноразовый токен предъявили второй раз.

Как завести

Раздел Вебхуки проекта — или запрос:

curl -s $BASE/projects/$PROJECT/webhooks \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"url":"https://ops.example.com/envmaster","eventTypes":["entry.updated","rotation.failed","enrollment.reuse-detected"]}'

Нужны адрес, список событий и, если хотите, список окружений (environments) — пустой означает «все».

Секрет подписи возвращается в ответе один раз, при создании. В базе он хранится зашифрованным и повторно не показывается: если потеряется, вебхук придётся завести заново. Право — проектный admin; последние 50 доставок со статусами смотрятся в кабинете или GET /projects/:id/webhooks/:id/deliveries.

Что приходит

POST с телом:

{
  "event": "entry.updated",
  "projectId": "prj_...",
  "environment": "prod",
  "occurredAt": "2026-08-27T10:15:00.000Z",
  "metadata": { "key": "DB_PASSWORD", "kind": "secret", "version": 4 }
}

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

Проверка подписи

Заголовок x-signature содержит sha256=<hex> — HMAC-SHA256 от тела запроса на вашем секрете.

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header ?? "");
  const b = Buffer.from(expected);
  // Длины сравниваются отдельно: timingSafeEqual бросает на разных длинах.
  return a.length === b.length && timingSafeEqual(a, b);
}

Считайте HMAC от сырого тела, до разбора JSON. Пересобранный из объекта JSON почти наверняка отличается пробелами или порядком полей, и подпись не сойдётся.

Сравнивайте за постоянное время. Обычное === на строках выходит раньше при первом несовпавшем байте, и по времени ответа подпись можно подобрать.

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

События

СобытиеКогда приходит
entry.createdв окружении появился новый ключ
entry.updatedзначение изменено, включая откат на прошлую версию
entry.deletedключ удалён
entry.rotatedротация прошла успешно
rotation.failedротация не удалась; значение осталось прежним
secret.near-expiryдо истечения секрета осталось мало времени
enrollment.reuse-detectedenrollment-токен предъявлен второй раз
validation.failedокружение не прошло проверку по схеме

Тип identity.login в списке допустимых событий зарегистрирован, но сейчас не отправляется — входы identity смотрите в журнале аудита (identity.login, с флагом newIp на новом адресе). Подписываться на него бессмысленно, доставок не будет.

Два события стоит подключить, даже если остальные не нужны.

rotation.failed означает, что автоматическая замена не удалась и сервис работает на старом значении. Без уведомления это выясняется в тот день, когда старое значение перестанет действовать.

enrollment.reuse-detected означает, что одноразовым токеном воспользовались дважды. Один из двух — не вы. Сервер в этот момент уже отказал во втором обмене, но identity сам не отзывает — это ваше действие: отозвать identity и выпустить новую. Разбор — на странице Сервисы и identity.

Доставка и повторы

Первая попытка — сразу. При неудаче (сетевой сбой или ответ не 2xx) сервер повторяет с растущими паузами: 1 с, 10 с, 1 мин, 5 мин, всего до пяти попыток.

Отвечайте 2xx как можно раньше, до тяжёлой работы. Приёмник, который сначала пересобирает контейнер и только потом отвечает, соберёт его пять раз.

Обработчик должен быть идемпотентным: повтор — это та же доставка, а не новое событие.

Куда можно слать

Только HTTPS. Внутренние, loopback- и link-local-адреса в проде отклоняются: сервер, которому можно указать любой адрес, превращается в сканер вашей сети из чужих рук.

Адрес проверяется по IP после разрешения имени, а не по тексту — иначе запрет обходился бы доменом, указывающим на 127.0.0.1. Проверка повторяется перед каждой попыткой доставки, а не только при сохранении: DNS, поменявшийся после настройки, дверь не открывает.

Общие секреты

Если окружение читает ключи другого проекта по ссылке, изменение общего значения приходит и на его вебхуки — с полем fromProjectId в метаданных, чтобы было видно, что ключ пришёл извне.

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

Выключить, не удаляя

У вебхука есть переключатель (PATCH /webhooks/:id, поле enabled). Выключенный сохраняет адрес, секрет и подписки, но не получает доставок — удобно, когда приёмник на обслуживании и заваливать его повторами незачем.