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
- Przejdź do Alerty → Dodaj kanał → Webhook i wklej swój endpoint. Musi używać
httpsi być publicznie dostępny. Nie wysyłamy na adresy prywatne ani lokalne i nie podążamy za przekierowaniami. - Twój sekret podpisu (
whsec_…) jest pokazywany od razu. Zapisz go po swojej stronie. - 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.