Dokumentacja

Webhooki

Kanał webhook wysyła każdy alert jako żądanie JSON POST na Twój endpoint. Każde żądanie jest podpisane, więc możesz sprawdzić, czy naprawdę pochodzi od nas.

Konfiguracja

  1. Przejdź do Alerty → Dodaj kanał → Webhook i wklej swój endpoint. Musi używać https i być publicznie dostępny. Nie wysyłamy na adresy prywatne ani lokalne i nie podążamy za przekierowaniami.
  2. Twój sekret podpisu (whsec_…) jest pokazywany od razu. Zapisz go po swojej stronie.
  3. Wysyłamy żądanie testowe. Gdy tylko Twój endpoint odpowie dowolnym kodem 2xx, kanał zostaje potwierdzony i zaczyna otrzymywać alerty. Przy każdej innej odpowiedzi kanał i tak zostaje dodany wraz z sekretem jako Niepotwierdzony: dodaj sekret do endpointu i kliknij Ponów żądanie do endpointu.

Żądanie

POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-PulseKeeper-Timestamp: 1790479747
X-PulseKeeper-Signature: 5d41402abc4b2a76b9719d911017c592…
X-PulseKeeper-Delivery: ntf_1842
Nagłówek Znaczenie
X-PulseKeeper-Timestamp Czas Unix, w którym podpisaliśmy żądanie.
X-PulseKeeper-Signature Szesnastkowy HMAC-SHA256 z timestamp.body z Twoim sekretem. Podczas rotacji sekretu zawiera dwa podpisy rozdzielone przecinkiem.
X-PulseKeeper-Delivery ID tej wiadomości. Pozostaje takie samo przy każdej ponownej próbie, więc możesz ignorować duplikaty.

Weryfikacja podpisu

Oblicz HMAC ze znacznika czasu, kropki i surowej treści żądania, a następnie porównaj go z każdym podpisem w nagłówku. Odrzucaj żądania starsze niż 5 minut.

PHP

$timestamp = $request->header('X-PulseKeeper-Timestamp');
$expected  = hash_hmac('sha256', $timestamp.'.'.$request->getContent(), $secret);

$valid = abs(time() - (int) $timestamp) < 300
    && collect(explode(',', $request->header('X-PulseKeeper-Signature')))
        ->contains(fn ($signature) => hash_equals($expected, trim($signature)));

JavaScript

import crypto from 'node:crypto';

const timestamp = req.headers['X-PulseKeeper-Timestamp'.toLowerCase()];
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const valid = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
  && req.headers['X-PulseKeeper-Signature'.toLowerCase()].split(',')
       .some((s) => crypto.timingSafeEqual(Buffer.from(s.trim()), Buffer.from(expected)));

Użyj surowej treści dokładnie w takiej postaci, w jakiej ją otrzymałeś: ponowne zakodowanie JSON-a ją zmienia i podpis nie będzie się zgadzał. Używaj porównania w stałym czasie (hash_equals, timingSafeEqual).

Rotacja sekretu

Zmień sekret tworzy nowy sekret. Przez kolejne 24 godziny podpisujemy każde żądanie zarówno nowym, jak i starym sekretem, więc możesz wdrożyć nowy bez utraty żadnego alertu.

Zdarzenia

Pole event mówi, co się stało, więc Twój endpoint nie musi przechowywać stanu:

Zdarzenie Kiedy
incident.alert Incydent zostaje potwierdzony (pierwszy alert i późniejsze kroki eskalacji).
incident.reminder Incydent jest nadal otwarty. Przypomnienia są coraz rzadsze, zobacz progi i przypomnienia.
incident.recovery Incydent zostaje rozwiązany. Wysyłane na każdy kanał, który dostał alert. Wyjątkiem jest certyfikat lub domena odnowione przed wygaśnięciem: e-mail i czat milczą, a webhook i tak dostaje to zdarzenie, aby można było zamknąć to, co otworzono przy incident.alert. Który to przypadek, mówi incident.type (ssl_expiring, domain_expiring).
monitor.never_connected Nowy cel nigdy nie odpowiedział, zwykle z powodu literówki lub firewalla.
deployment.regressed Wdrożenie było wyraźnie wolniejsze lub cięższe niż w godzinie przed nim.
notifications.batch Kilka zdarzeń zgrupowanych w jedną wiadomość (godziny ciszy lub wiele alertów naraz).
channel.test Kliknąłeś Wyślij test. Obok przycisku możesz wybrać dowolne zdarzenie powyżej: otrzymasz jego przykład z "test": true na górze i zmyślonym monitorem na shop.example.com.

Zdarzenie incydentu wygląda tak:

{
  "event": "incident.alert",
  "incident": {
    "id": 128,
    "type": "downtime",
    "severity": "critical",
    "state": "confirmed",
    "started_at": "2026-09-27T12:24:02+00:00",
    "confirmed_at": "2026-09-27T12:25:02+00:00",
    "resolved_at": null,
    "duration_seconds": null,
    "error_code": "http_5xx",
    "affected_locations": ["fra", "nyc"],
    "healthy_locations": [],
    "url": "http://pulsekeeper.io/domain/3f9a1c07e4b2d815?tab=incidents"
  },
  "monitor": {
    "id": 42,
    "type": "http",
    "name": "shop.example.com",
    "hostname": "shop.example.com",
    "url": "https://shop.example.com"
  }
}

Czasy są w formacie ISO 8601 w UTC. severity ma wartość critical dla awarii i warning dla spraw, które wymagają uwagi, ale nie są awarią: wygasającego certyfikatu, firewalla blokującego nasze sprawdzenia lub wolniejszego wdrożenia.

Ponowne próby

Jeśli Twój endpoint nie odpowie w ciągu 8 sekund albo zwróci cokolwiek innego niż 2xx, próbujemy ponownie po 10 sekundach i po kolejnej minucie – łącznie trzy próby, wszystkie z tym samym X-PulseKeeper-Delivery. Ostatni błąd jest pokazywany przy kanale na stronie Alerty.