
Интеграция Pingvera с Helpdesk должна создавать одну управляемую карточку инцидента, обновлять её при изменении состояния и закрывать после подтверждённого восстановления. Она не должна создавать новый тикет на каждый повтор проверки.
Надёжный маршрут выглядит так:
Pingvera
↓ HTTPS webhook
входной адаптер
↓ проверка + сохранение
очередь событий
↓ нормализация + дедупликация
правила критичности и маршрутизации
↓ API
Helpdesk: создать / обновить / закрыть
Прямое соединение «webhook сразу в создание тикета» подходит только для простого прототипа. В production между системами нужен хотя бы слой, который умеет проверять запрос, переживать повторы и помнить связь между событием и заявкой.
Pingvera отправляет вебхук POST-запросом, Content-Type: application/json, User-Agent: PingveraNotifier/1.0. Официальный payload:
{
"event": "opened",
"severity": "critical",
"summary": "host offline",
"incident": "inc_01J...",
"org": "ООО Ромашка",
"entity": "acme.com · Доступность",
"target": "https://acme.com",
"link": "https://app.pingvera.ru/#/incidents/inc_01J...",
"started_at": "2026-07-18T10:15:00Z"
}
Поля:
event — opened | resolved | test | digest;severity — critical | warning | info;summary — краткое описание, может быть пустым;incident — публичный ID инцидента вида inc_...; пустой для test и digest;org — название организации-клиента;entity — что именно проверяется (например, acme.com · Доступность);target — цель проверки;link — ссылка на карточку инцидента в дашборде;started_at — время начала в RFC3339 UTC.У payload нет отдельного delivery_id или event_id — единственный идентификатор в теле это incident (public ID), и он пустой для служебных типов test/digest. Это меняет то, как строить дедупликацию и incident key ниже: их придётся собирать из incident+event, а для доставок без incident — из хеша raw body.
Это официальная схема продукта на дату проверки статьи, а не реконструкция по одной тестовой доставке. Тем не менее ниже статья по-прежнему рекомендует прогнать реальную доставку через свой тестовый endpoint: схема может расшириться новыми необязательными полями (правило продукта — protobuf/API эволюционируют только добавлением полей, не ломающими изменениями), и адаптер должен переживать такие добавления, не считая их ошибкой.
Порядок внедрения:
2xx;Для P1 webhook не должен быть единственным каналом. Сбой адаптера или Helpdesk не должен лишать дежурного резервного уведомления.
Не каждое наблюдение должно становиться заявкой.
Пример таблицы:
| Событие | Действие Helpdesk | Срочное уведомление |
|---|---|---|
| Единичная неудачная проверка | Сохранить для подтверждения, тикет не создавать | Нет |
| Подтверждённая потеря критической функции | Создать инцидент | По маршруту P1/P2 |
| Повтор того же активного сбоя | Обновить существующую карточку по правилам | Только при эскалации |
| Изменение влияния | Обновить приоритет и комментарий | Если стало P1 |
| Восстановление одной из зависимых проверок | Обновить контекст | Обычно нет |
| Подтверждённое восстановление функции | Перевести в наблюдение или решить | По регламенту |
| Плановое окно | Не создавать либо пометить как maintenance | Нет |
| Истекающий домен/SSL | Создать плановую задачу | По сроку, не как падение |
| Нет heartbeat | Создать задачу после допустимого окна | По критичности процесса |
Критичность определяется не одним HTTP-кодом. Нужны:
Используйте общую шкалу P1–P4, чтобы приоритет в Pingvera, Helpdesk и регламенте означал одно и то же.
Мониторинг сообщает о проверке и состоянии. Helpdesk ожидает тему, описание, проект, очередь, приоритет и исполнителя.
Webhook может приходить для каждого изменения. Helpdesk хранит одну карточку инцидента с комментариями и статусами.
Поставщики webhook могут повторять событие после ошибки, таймаута или ручной переотправки. Получатель обязан безопасно обработать один и тот же ID несколько раз.
Даже если текущий поставщик обычно доставляет последовательно, сеть, очереди и retry могут изменить порядок. Храните время события и проверяйте допустимый переход состояния.
Входное событие нельзя терять только потому, что API тикетов ответил 503.
Публичный endpoint нужно защищать от поддельных запросов, replay, слишком больших payload и утечки секретов.
Принимает только ожидаемый метод и content type. Ограничивает размер тела, сохраняет raw body для проверки подписи и не выполняет долгую работу в запросе.
Pingvera поддерживает HMAC-подпись: если у webhook-канала задан секрет, к запросу добавляются заголовки X-Pingvera-Signature: sha256=<hex> (HMAC-SHA256 от секрета и raw body) и X-Pingvera-Timestamp: <unix-секунды> (информационный, в саму подпись не входит). Секрет с префиксом whsec_ показывается один раз — в ответе POST /api/v1/channels при создании канала — и больше нигде не отдаётся.
Каналы, созданные до появления подписи, приходят без этих двух заголовков — доставка при этом не ломается, но и проверить источник по подписи для них нельзя. Если у канала подписи нет, изучите доступные варианты:
Уникальный URL-токен может попасть в журналы и историю. Не помещайте в query string другие секреты. IP allowlist полезен только как дополнительная мера и требует обновления.
До ответа источнику событие записывается в устойчивое хранилище:
delivery_id;После успешной записи можно ответить 202 Accepted или другим документированным успешным кодом.
Worker независимо:
Хранит:
incident_key → helpdesk_ticket_id → last_source_state → last_event_time
Без неё интеграция создаёт дубликаты.
Официальная схема приведена выше. Реальную тестовую доставку всё равно стоит записать — не чтобы угадать поля, а чтобы увидеть, как заполнены org/entity/summary для конкретных типов проверок в вашем аккаунте, и убедиться, что адаптер корректно обрабатывает test/digest с пустым incident.
Не используйте публичный request-bin для клиентских production-событий: payload может содержать URL, названия проектов и операционные детали.
Поднимите контролируемый endpoint в тестовом контуре.
Он должен временно сохранить:
Перед тестом:
Отправьте минимум три состояния, если интерфейс позволяет:
Сравните с описанной схемой:
incident (ожидаемо — пустой только для test/digest);entity для разных типов проверки (http/tcp/dns/tls/wp/domain/links/heartbeat);summary в вашем случае;X-Pingvera-Signature/X-Pingvera-Timestamp (зависит от того, задан ли секрет у канала);500 и таймауте на вашей стороне.Поведение retry (10 секунд на ответ, до 8 попыток с exponential backoff) проверяйте безопасным тестом, не вызывая искусственную ошибку для реального P1.
После исследования преобразуйте любой входящий формат в стабильную схему студии.
{
"schema_version": "studio.incident-event/v1",
"source": "pingvera",
"delivery_id": "delivery-or-derived-id",
"event_id": "source-event-id",
"event_type": "check.failed_confirmed",
"occurred_at": "2026-08-04T12:10:00Z",
"received_at": "2026-08-04T12:10:02Z",
"project_id": "WEB-0042",
"check_id": "source-check-id",
"check_key": "lead-form",
"incident_key": "WEB-0042:lead-form:source-incident-id",
"state": "open",
"severity_hint": "P2",
"summary": "Не подтверждается доставка тестовой заявки",
"target": "https://example.ru/contact/",
"observations": {
"regions_failed": 2,
"last_status": 200,
"reason_code": "delivery_not_confirmed"
},
"details_url": "https://app.pingvera.ru/..."
}
Это пример внутреннего контракта студии, а не зеркало payload Pingvera. event_id/delivery_id в схеме Pingvera нет — заполняйте delivery_id производным значением (например, хешем raw body или incident:event:started_at), а event_id — из поля event. project_id/check_id/check_key — сопоставление по вашему каталогу проектов, incident_key собирается из incident, как описано выше, state/severity_hint — из event/severity реального payload.
В Helpdesk обычно достаточно факта, влияния, времени, результата проверки и защищённой ссылки на подробности.
Лучший вариант — стабильный ID инцидента от источника. У Pingvera это поле incident (inc_...) в payload opened/resolved:
pingvera:<incident>
Для событий test и digest поле incident пустое — на них incident key не строится, это не бизнес-инцидент. Если по какой-то причине incident недоступен, используйте контролируемую корреляцию:
<project_id>:<check_key>:<opened_period>
Нельзя использовать только название проверки: оно может измениться и повторяться.
Правила:
Сделайте уникальный индекс по source + delivery_id.
Псевдокод:
if inbox.contains(source, delivery_id):
return 2xx
persist(raw_event, status="received")
return 202
В worker:
event = normalize(raw_event)
if processed_events.contains(event.event_id, event.event_type):
mark_duplicate()
stop
apply_state_transition(event)
mark_processed()
Почему два уровня:
Stripe в своей документации прямо рекомендует хранить обработанные event ID и игнорировать повтор с успешным ответом. Это общий паттерн, а не описание конкретного поведения Pingvera.
Не обновляйте тикет произвольным текстом. Определите допустимые переходы.
unknown
↓ confirmed failure
open
↓ repeated failure
open + observation
↓ partial recovery
monitoring
↓ confirmed recovery
resolved
↓ new confirmed failure
new occurrence
Событие с более старым occurred_at не должно возвращать карточку из resolved в open без отдельной логики.
Храните:
При сомнении отправьте событие на ручную проверку, а не молча изменяйте P1.
| Поле тикета | Источник |
|---|---|
| Тема | Класс + проект + нарушенная функция |
| Описание | Влияние, время, подтверждение, URL, наблюдения |
| Очередь | Владелец проекта или тип услуги |
| Приоритет | Правила P1–P4, не только severity hint |
| Клиент | project_id из каталога |
| Теги | pingvera, тип проверки, класс проекта |
| Внешний ID | incident_key |
| Исполнитель | Дежурная роль |
| Ссылка | Карточка Pingvera/проекта |
Пример темы:
[P2][WEB-0042] Не подтверждается доставка заявки с формы
Пример описания:
Pingvera подтвердила нарушение критической функции.
- Проект: WEB-0042 — Магазин «Север»
- Функция: доставка заявки с формы
- Начало по источнику: 15:10 МСК
- Подтверждение: 2 повторные проверки
- Пользовательское влияние: тестовая заявка не обнаружена в целевом ящике
- Состояние: открыто
- Паспорт проекта: [внутренняя ссылка]
- Подробности мониторинга: [защищённая ссылка]
Первый шаг: выполнить runbook `lead-delivery-v2`.
Не пишите «клиент теряет миллионы», если мониторинг этого не измеряет.
Добавляйте комментарий только при значимом изменении:
Не добавляйте комментарий каждую минуту «по-прежнему не работает» без полезного контекста. Счётчик повторов можно обновлять агрегированно.
После recovery не всегда нужно сразу закрывать заявку.
Рабочая последовательность:
source recovered → статус «Наблюдение» → контрольные проверки → «Решено»
В комментарии:
Пример правил:
rules:
- when:
service_class: A
check_key: checkout
state: open
priority: P1
queue: oncall
- when:
service_class: B
check_type: synthetic_form
state: open
priority: P2
queue: support
- when:
event_type: certificate.expiring
days_left_gte: 14
priority: P3
queue: planned-work
Поля и DSL условны. Важно хранить правила отдельно от кода транспорта и покрывать их тестами.
Проверочные сценарии:
Используйте действующий сертификат и не отключайте его проверку.
Если у webhook-канала Pingvera задан секрет (whsec_..., выдаётся один раз при создании канала), запрос несёт X-Pingvera-Signature: sha256=<hex> и X-Pingvera-Timestamp: <unix-секунды>:
X-Pingvera-Signature, отрежьте префикс sha256=;HMAC-SHA256(secret, raw_body) и сравните с полученным значением;hmac.Equal/hash_equals/hmac.compare_digest), не строковым ==;X-Pingvera-Timestamp в саму подпись не входит — это информационное поле; если вам нужно окно свежести против replay, определите его сами на основе этого заголовка, так как Pingvera такое окно не документирует;Поскольку secret отдаётся только один раз при создании канала, конкретный порядок ротации (пересоздание канала целиком или отдельная операция смены секрета) нужно уточнить в актуальном интерфейсе Pingvera — статья этого не утверждает.
POST;Content-Type;incident+event+started_at);X-Pingvera-Timestamp как справочный сигнал для собственного допустимого окна, если вы его вводите;Не заставляйте worker автоматически загружать любой target или details_url из payload. Если нужны дополнительные данные, используйте заранее разрешённый API hostname и собственный идентификатор.
Исходящий API-токен:
Фрагмент реализует фактический контракт Pingvera: заголовок X-Pingvera-Signature: sha256=<hex>, алгоритм HMAC-SHA256 от секрета и raw body. Если у конкретного канала секрета нет (старый канал, созданный до появления подписи), заголовка не будет — это ожидаемо, а не ошибка.
import crypto from "node:crypto";
function safeEqual(a, b) {
const left = Buffer.from(a, "utf8");
const right = Buffer.from(b, "utf8");
if (left.length !== right.length) return false;
return crypto.timingSafeEqual(left, right);
}
export function verifyPingveraSignature({ rawBody, signatureHeader, secret }) {
if (!signatureHeader || !secret) return false;
const [scheme, hex] = signatureHeader.split("=");
if (scheme !== "sha256" || !hex) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return safeEqual(hex, expected);
}
Проверка выполняется по неизменённому raw body. Если middleware сначала разобрал JSON, а потом сериализовал его заново, подпись может не совпасть.
async function receiveWebhook(request, response) {
const rawBody = await readRawBody(request, { maxBytes: 256_000 });
if (!isExpectedContentType(request.headers["content-type"])) {
return response.status(415).end();
}
if (!verifyPingveraSignature({
rawBody,
signatureHeader: request.headers["x-pingvera-signature"],
secret: channelSecret
})) {
return response.status(401).end();
}
const payload = parseJson(rawBody);
// У payload Pingvera нет отдельного delivery ID — берём производный ключ:
// incident (пустой для test/digest) + event + started_at, либо хеш raw body.
const deliveryId = payload.incident
? `${payload.incident}:${payload.event}:${payload.started_at}`
: sha256(rawBody);
const inserted = await inbox.insertIfAbsent({
source: "pingvera",
deliveryId,
receivedAt: new Date().toISOString(),
rawBodyEncrypted: encryptForInbox(rawBody),
bodyHash: sha256(rawBody),
status: "received"
});
// Повтор уже принят: вернуть успех и не создавать вторую работу.
if (!inserted) return response.status(200).end();
await queue.publish({ source: "pingvera", deliveryId });
return response.status(202).end();
}
Функции хранения, шифрования, очереди и проверки контракта нужно реализовать выбранными технологиями. In-memory Set не подходит для production: после перезапуска дедупликация исчезнет.
async function processDelivery(message) {
const raw = await inbox.get(message.source, message.deliveryId);
const sourcePayload = JSON.parse(decrypt(raw.rawBodyEncrypted));
// event: "opened" | "resolved" | "test" | "digest" (реальные значения Pingvera).
if (sourcePayload.event === "test" || sourcePayload.event === "digest") {
return inbox.markProcessed(message.deliveryId); // не бизнес-инцидент — тикет не создаём.
}
const event = normalizePingveraPayload(sourcePayload);
validateInternalSchema(event);
if (await processed.exists(event.source, event.eventId, event.eventType)) {
return inbox.markDuplicate(message.deliveryId);
}
const current = await incidents.findActive(event.incidentKey);
if (event.state === "open" && !current) {
const ticket = await helpdesk.create(buildTicket(event), {
idempotencyKey: event.incidentKey
});
await incidents.bind(event.incidentKey, ticket.id, event);
} else if (event.state === "open" && current) {
await helpdesk.addMeaningfulUpdate(current.ticketId, event);
await incidents.advance(current, event);
} else if (event.state === "resolved" && current) {
await helpdesk.moveToMonitoringOrResolve(current.ticketId, event);
await incidents.advance(current, event);
} else {
await deadLetter.store(event, "unexpected_state_transition");
}
await processed.mark(event);
await inbox.markProcessed(message.deliveryId);
}
idempotencyKey сработает только если конкретный Helpdesk поддерживает такой механизм. Иначе используйте внешний ID, поиск перед созданием и собственную таблицу соответствия.
Endpoint и поля условны.
POST /api/tickets HTTP/1.1
Host: helpdesk.example.ru
Authorization: Bearer <token-from-vault>
Content-Type: application/json
Idempotency-Key: pingvera:WEB-0042:lead-form:incident-123
{
"queue": "website-support",
"priority": "P2",
"subject": "[P2][WEB-0042] Не доставляется тестовая заявка",
"description": "...",
"external_id": "pingvera:WEB-0042:lead-form:incident-123",
"tags": ["pingvera", "synthetic-form", "WEB-0042"]
}
Проверьте официальную документацию вашего Helpdesk:
Не выводите токен в командную строку общего CI, если он попадёт в историю или список процессов.
Для небольшой студии адаптер можно собрать в n8n, если self-hosted или выбранный контур соответствует требованиям к данным и доступам.
Рабочий workflow:
Webhook Trigger
↓
проверка source authentication
↓
сохранение delivery ID в Data Store / БД
↓
Respond to Webhook: 202
↓
нормализация полей
↓
загрузка project_id из каталога
↓
поиск incident_key → ticket_id
↓
Switch: create / update / resolve / ignore
↓
HTTP Request в Helpdesk
↓
запись результата и метрик
Особые проверки:
Если raw body недоступен или изменяется, HMAC-проверку лучше выполнить на reverse proxy/функции перед n8n.
Повторять с ограниченным exponential backoff и jitter:
429 с учётом Retry-After;502, 503, 504;Не повторять бесконечно:
401/403 до исправления credentials;Отправлять в dead-letter queue с уведомлением владельцу.
Helpdesk мог создать тикет, но ответ потерялся. Перед новой попыткой ищите по external ID или используйте idempotency key. Иначе появятся дубликаты.
2xx — событие надёжно принято или уже было принято;4xx — запрос не пройдёт без изменения;5xx — временно не удалось сохранить приём.Pingvera ждёт ответ 10 секунд; любой статус ≥300 или таймаут считается неудачей. Неудачи повторяются с exponential backoff до 8 попыток общей продолжительностью около 2 часов 15 минут, после чего доставка помечается окончательно неудавшейся — дальше повторов не будет, и это единственный источник события до следующего изменения состояния. Отсюда следует, что 2xx нужно возвращать быстро (входной обработчик не должен ждать вызова Helpdesk синхронно) и не возвращать 2xx, если событие потеряно до устойчивого хранения — иначе оно не переотправится.
Интеграция мониторинга тоже может сломаться.
Контролируйте:
2xx/4xx/5xx;Создайте синтетический тест:
Не запускайте такой тест в боевую очередь P1 без специальной маркировки.
# Runbook: Pingvera → Helpdesk
## Владелец
- Основной:
- Резерв:
- Канал:
## Компоненты
- Webhook URL:
- Inbox:
- Queue:
- Worker:
- Helpdesk project/queue:
- Vault refs:
## Проверка здоровья
1. Проверить ingress 2xx.
2. Проверить возраст очереди.
3. Проверить dead-letter.
4. Проверить Helpdesk API.
5. Найти delivery ID.
## Повторная обработка
- Кто одобряет:
- Команда/интерфейс:
- Как проверить дедупликацию:
- Как не создать второй тикет:
## Ротация
- Секрет входящего webhook:
- Токен Helpdesk:
- Порядок двухключевого периода:
## Аварийный режим
- Резервный канал P1:
- Ручное создание тикета:
- Как пометить пропущенные события:
## Контрольный тест
- Последний результат:
- Следующая дата:
2xx.Очередь заполняется дубликатами. Нужны incident key и таблица соответствия.
2xx до сохраненияПроцесс падает, событие теряется, источник считает доставку успешной. Сначала persistent inbox.
Helpdesk тормозит, отправитель получает timeout и повторяет событие. Отвечайте после приёма, обрабатывайте асинхронно.
Публичный endpoint создаёт поддельные P1. Проверяйте доступный документированный механизм источника.
Он попадает в access logs и историю. Используйте безопасный механизм, если продукт его поддерживает, и защищайте сам URL.
Старое recovery приходит после нового failure. Учитывайте occurrence, время и допустимые переходы.
Туда попадают секреты, HTML и чувствительные данные. Используйте нормализованное безопасное описание.
Вебхук перестал создавать тикеты, но команда узнаёт об этом на реальном сбое. Нужен контрольный тест.
Pingvera остаётся источником фактов о наблюдаемой функции:
Helpdesk становится системой работы:
Адаптер связывает их, но не должен автоматически объявлять техническую гипотезу причиной или рассчитывать финансовое влияние без данных.
Начните с безопасного теста: создайте отдельный webhook-канал в Pingvera, запишите фактический payload и проведите событие через тестовую очередь Helpdesk до включения production-маршрута.
Да, если Helpdesk принимает точный формат, умеет аутентифицировать источник, дедуплицировать и сопоставлять состояния. На практике небольшой адаптер или n8n-flow обычно нужен для нормализации и связи тикетов.
POST с Content-Type: application/json, полями event (opened/resolved/test/digest), severity (critical/warning/info), summary, incident (inc_..., пустой для test/digest), org, entity, target, link и started_at (RFC3339 UTC). Полная схема — в разделе «Реальная схема webhook Pingvera» выше. Внутренний нормализованный контракт студии — это уже следующий шаг, отдельный от формата Pingvera.
Для production нужна устойчиво хранимая связь delivery/event/incident/ticket. Это может быть реляционная БД, надёжный key-value store или возможности интеграционной платформы. Память процесса недостаточна.
Pingvera поддерживает подпись (X-Pingvera-Signature), но только для каналов, у которых задан секрет: у каналов, созданных до появления этой функции, заголовков не будет. Если ваш канал реально без подписи, используйте наиболее сильный доступный механизм: секретный заголовок, авторизацию, mTLS, allowlist или защищённый gateway — и рассмотрите пересоздание канала с секретом, если это не критично для существующих интеграций.
2xx?После проверки запроса и надёжной записи события либо при подтверждённом дубликате. Не обязательно ждать вызова Helpdesk, если он выполняется очередью.
Хранить delivery ID, event ID и incident key, а также связь с ticket ID. При повторе возвращать успех и обновлять существующую карточку только при новом значимом состоянии.
Для простого события можно перевести его в «Наблюдение», а закрыть после нескольких успешных проверок или подтверждения инженера. Правило зависит от критичности и процесса.
Да, если вы проверили raw body, аутентификацию, устойчивое хранение, error workflow, секреты и доступы редакторов. Для критичного потока одного визуального happy path недостаточно.
Webhook — это транспорт события, а не готовый процесс инцидента.
Надёжная интеграция строится так:
проверить → сохранить → подтвердить приём → нормализовать → дедуплицировать → сопоставить → изменить тикет → наблюдать
Если начать с этого каркаса, повторная доставка не создаст десять заявок, недоступность Helpdesk не потеряет P1, а восстановление обновит правильную карточку.
Следующая учебная ступень: «Telegram, email или MAX: как не зависеть от одного канала уведомлений».
Pingvera следит за сайтами клиентов «снаружи и изнутри» — доступность из регионов, формы, оплата, домен, SSL, сервер — и предупреждает в Telegram, email и Max раньше, чем напишет клиент.
Попробовать Pingvera бесплатноЧитайте также: Terraform-провайдер Pingvera: мониторинг как код · CLI Pingvera: мониторинг из терминала и скриптов · Как перенести статус-страницу на Pingvera за 10 минут · Ping-Admin или Pingvera: что выбрать студии и владельцу сайта · Бесплатно проверить сайт.