diff --git a/apps/web/src/views/docs.ejs b/apps/web/src/views/docs.ejs index f1b67fe..5f22844 100644 --- a/apps/web/src/views/docs.ejs +++ b/apps/web/src/views/docs.ejs @@ -57,6 +57,9 @@
Account Monitors + Reliability + Notifications + Webhook payload Fields @@ -135,6 +138,11 @@ "request_body": "{\"ping\": true}", // optional — Content-Type defaults to application/json "regions": ["eu-central", "us-west"], // optional — default: all regions "timeout_ms": 10000, // optional — default: 10000 + "max_retries": 2, // optional — retry N times before declaring DOWN. Default: 0 + "retry_interval_s": 30, // optional — seconds between retries. Default: 30 + "resend_interval": 10, // optional — re-alert every Nth consecutive DOWN beat. 0 = never. Default: 0 + "cert_alert_days": 14, // optional — alert when TLS cert is within N days of expiry. 0 disables. Default: 14 + "channel_ids": ["<uuid>"], // optional — notification channels to attach "query": { ... } // optional — see Query Language below } @@ -149,6 +157,11 @@eu-central, us-west. Default: allcert notification when the TLS certificate is within N days of expiring. 0 disables. Default: 14.Returns recent ping results for a monitor. Max 1000.
+Returns recent ping results for a monitor. Max 1000. Each ping carries an important boolean — true on status transitions and resend ticks (the beats that triggered notifications).
PingQL doesn't immediately fire on a single failed check. Three knobs let you tune how reactive vs. how stable the alerting is:
+ +If a check fails and max_retries is greater than zero, the runner waits retry_interval_s seconds and retries up to that many times before recording a DOWN result. A successful retry posts a single UP ping with meta.retries noting how many attempts it took. This kills almost all flapping caused by transient TCP resets, brief 5xx blips, or network jitter.
Every check is recorded, but the important flag on a ping is only set when the monitor's state changes (UP↔DOWN) for that region. Notifications fire on important beats only — never on every routine check. State is tracked independently per region: if us-west goes DOWN, only a subsequent us-west UP clears it. eu-central being healthy will not silence a us-west outage.
For long outages, set resend_interval to re-fire the notification every Nth consecutive DOWN beat. With resend_interval: 10, a still-broken monitor produces an extra alert every 10 down checks. 0 (the default) means: alert once on the transition, then stay quiet until recovery.
For HTTPS monitors PingQL extracts the TLS leaf certificate's days-until-expiry on every check. When that drops at or below cert_alert_days for the first time, a separate cert notification fires (one per region). The flag clears when the cert is renewed, so each renewal cycle gets exactly one alert. Set cert_alert_days: 0 to disable.
If you don't supply a query, the monitor is considered up only on a 2xx response. Redirects (3xx), client errors (4xx) and server errors (5xx) all count as DOWN. Use the QL if you want different behaviour.
Notification channels are reusable destinations attached to monitors. When an important beat fires (DOWN, recovery, or cert), each attached channel is dispatched. PingQL ships with a webhook provider; more (Discord, Slack, Email) are designed as drop-ins.
+ +Returns all channels for the authenticated account.
+ +{
+ "name": "On-call webhook",
+ "kind": "webhook",
+ "config": {
+ "url": "https://hooks.example.com/pingql",
+ "headers": { "X-Team": "infra" }, // optional
+ "secret": "shared-hmac-secret" // optional — signs payloads
+ },
+ "enabled": true // optional — default true
+}
+ | Field | Type | Description |
|---|---|---|
| name | string | Display name (up to 200 chars) |
| kind | string | Provider type. Currently only webhook. |
| config | object | Provider-specific config. For webhook, requires url (http/https). Optional headers object and secret for HMAC signing. |
| enabled | boolean | Disabled channels are skipped during dispatch but remain attached. |
All fields optional. Provide a partial body to change name, config, or enabled state.
+ +Removes the channel and all monitor attachments to it.
+ +Sends a synthetic test event through the channel and returns whether the provider accepted it. Useful to verify the URL and HMAC are wired correctly without waiting for a real outage.
Pass channel_ids as an array of channel UUIDs when creating or patching a monitor. The PATCH replaces the full set; pass an empty array to detach all channels. Channels can only be attached to monitors in the same account.
PATCH /monitors/abc123def456
+Authorization: Bearer <key>
+Content-Type: application/json
+
+{ "channel_ids": ["5fb1c0bf-…", "a72e0d91-…"] }
+ Webhook channels POST a JSON body to the configured URL on every event. The HTTP method is POST, the request times out after 5 seconds, and PingQL does not retry — the next important beat is the retry. Failures are logged but never block ingest.
| Header | Description |
|---|---|
| content-type | application/json |
| user-agent | PingQL-Notifier/1 |
| x-pingql-signature | Hex-encoded HMAC-SHA256 of the raw request body, keyed by config.secret. Only present when a secret is configured. Verify it server-side to confirm the request came from PingQL. |
| custom | Any headers from config.headers are forwarded as-is. |
Every payload has the same envelope:
+{
+ "channel": { "id": "<uuid>", "name": "On-call webhook" },
+ "event": { "kind": "down" | "up" | "cert" | "test", ... }
+}
+ down — fired on the first DOWN important beat for a region, and again every resend_intervalth consecutive down if configured.
{
+ "kind": "down",
+ "monitor": {
+ "id": "abc123def456",
+ "name": "My API",
+ "url": "https://api.example.com/health",
+ "region": "us-west" // "" for unspecified/single-region monitors
+ },
+ "ping": {
+ "status_code": 503,
+ "latency_ms": 412,
+ "error": null,
+ "checked_at": "2026-04-08T14:23:00.000Z"
+ }
+}up — fired on recovery, only when that same region transitions back from DOWN. Same shape as down.
cert — fired once per renewal cycle when the TLS leaf cert drops at or below cert_alert_days for a region.
{
+ "kind": "cert",
+ "monitor": { "id": "…", "name": "…", "url": "…", "region": "us-west" },
+ "days": 9 // days until certificate expires
+}test — synthetic event from POST /notifications/channels/:id/test. The monitor object is a placeholder.
{
+ "kind": "test",
+ "monitor": { "id": "test", "name": "Test event", "url": "https://example.com", "region": "" }
+}import { createHmac, timingSafeEqual } from "crypto"; + +function verify(rawBody, headerSig, secret) { + const expected = createHmac("sha256", secret).update(rawBody).digest("hex"); + return timingSafeEqual(Buffer.from(expected), Buffer.from(headerSig)); +}+
Always verify against the raw request body before parsing JSON.
A PingQL query is a JSON object evaluated against each ping. If it returns true, the monitor is up. Default (no query): up when status < 400.
A PingQL query is a JSON object evaluated against each ping. If it returns true, the monitor is up. Default (no query): up only on a 2xx status. Redirects and errors all count as DOWN.
| Field | Type | Description |
|---|