fix: docs

This commit is contained in:
nate 2026-04-09 20:58:31 +04:00
parent 21194371b8
commit 16dd0ea36e
1 changed files with 100 additions and 97 deletions

View File

@ -53,13 +53,11 @@
<div class="nav-section">Getting Started</div> <div class="nav-section">Getting Started</div>
<a href="#overview" class="nav-link">Overview</a> <a href="#overview" class="nav-link">Overview</a>
<a href="#auth" class="nav-link">Authentication</a> <a href="#auth" class="nav-link">Authentication</a>
<a href="#reliability" class="nav-link">Reliability &amp; noise</a>
<div class="nav-section">API Reference</div> <div class="nav-section">API Reference</div>
<a href="#account" class="nav-link">Account</a> <a href="#account" class="nav-link">Account</a>
<a href="#monitors" class="nav-link">Monitors</a> <a href="#monitors" class="nav-link">Monitors</a>
<a href="#notifications" class="nav-link">Notifications</a> <a href="#notifications" class="nav-link">Notifications</a>
<a href="#webhook-payload" class="nav-link">Webhook payload</a>
<a href="#status-pages" class="nav-link">Status Pages</a> <a href="#status-pages" class="nav-link">Status Pages</a>
<a href="#incidents" class="nav-link">Incidents</a> <a href="#incidents" class="nav-link">Incidents</a>
@ -71,6 +69,10 @@
<a href="#ql-logical" class="nav-link">Logical</a> <a href="#ql-logical" class="nav-link">Logical</a>
<a href="#ql-consider" class="nav-link">$consider</a> <a href="#ql-consider" class="nav-link">$consider</a>
<a href="#ql-examples" class="nav-link">Examples</a> <a href="#ql-examples" class="nav-link">Examples</a>
<div class="nav-section">Guides</div>
<a href="#reliability" class="nav-link">Reliability &amp; noise</a>
<a href="#webhook-payload" class="nav-link">Webhook payload</a>
</aside> </aside>
<!-- Main content --> <!-- Main content -->
@ -95,26 +97,6 @@
</div> </div>
<!-- Reliability --> <!-- Reliability -->
<div id="reliability" class="section">
<h2>Reliability &amp; alert noise</h2>
<p>PingQL doesn't immediately fire on a single failed check. A few knobs let you tune how reactive vs. how stable the alerting is. These are concepts you'll see referenced throughout the API reference below.</p>
<h3>Retries before DOWN</h3>
<p>If a check fails and <code>max_retries</code> is greater than zero, the runner waits <code>retry_interval_s</code> seconds and retries up to that many times <em>before</em> recording a DOWN result. A successful retry posts a single UP ping with <code>meta.retries</code> noting how many attempts it took. This kills almost all flapping caused by transient TCP resets, brief 5xx blips, or network jitter.</p>
<h3>Important beats &amp; transitions</h3>
<p>Every check is recorded, but the <code>important</code> flag on a ping is only set when the monitor's state changes (UP↔DOWN) <em>for that region</em>. Notifications fire on important beats only — never on every routine check. State is tracked independently per region: if <code>us-west</code> goes DOWN, only a subsequent <code>us-west</code> UP clears it. <code>eu-central</code> being healthy will not silence a <code>us-west</code> outage.</p>
<h3>Resend interval</h3>
<p>For long outages, set <code>resend_interval</code> to re-fire the notification every Nth consecutive DOWN beat. With <code>resend_interval: 10</code>, a still-broken monitor produces an extra alert every 10 down checks. <code>0</code> (the default) means: alert once on the transition, then stay quiet until recovery.</p>
<h3>Cert expiry alerting</h3>
<p>For HTTPS monitors PingQL extracts the TLS leaf certificate's days-until-expiry on every check. When that drops at or below <code>cert_alert_days</code> for the first time, a separate <code>cert</code> notification fires (one per region). The flag clears when the cert is renewed, so each renewal cycle gets exactly one alert. Set <code>cert_alert_days: 0</code> to disable.</p>
<h3>Default empty query</h3>
<p>If you don't supply a <code>query</code>, the monitor is considered up only on a <strong style="color:#4ade80">2xx</strong> response. Redirects (3xx), client errors (4xx) and server errors (5xx) all count as DOWN. Use the QL if you want different behaviour.</p>
</div>
<!-- Account --> <!-- Account -->
<div id="account" class="section"> <div id="account" class="section">
<h2>Account</h2> <h2>Account</h2>
@ -267,81 +249,6 @@ Content-Type: application/json
</div> </div>
</div> </div>
<!-- Webhook payload -->
<div id="webhook-payload" class="section">
<h2>Webhook payload</h2>
<p>Webhook channels POST a JSON body to the configured URL on every event. The HTTP method is <code>POST</code>, 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.</p>
<h3>Headers</h3>
<table>
<thead><tr><th>Header</th><th>Description</th></tr></thead>
<tbody>
<tr><td>content-type</td><td><code>application/json</code></td></tr>
<tr><td>user-agent</td><td><code>PingQL-Notifier/1</code></td></tr>
<tr><td>x-pingql-signature</td><td>Hex-encoded HMAC-SHA256 of the raw request body, keyed by <code>config.secret</code>. Only present when a secret is configured. Verify it server-side to confirm the request came from PingQL.</td></tr>
<tr><td><em>custom</em></td><td>Any headers from <code>config.headers</code> are forwarded as-is.</td></tr>
</tbody>
</table>
<h3>Body shape</h3>
<p>Every payload has the same envelope:</p>
<div class="cb">
<div class="cb-header"><span class="cb-lang">json</span></div>
<pre>{
<span class="k">"channel"</span>: { <span class="k">"id"</span>: <span class="s">"&lt;uuid&gt;"</span>, <span class="k">"name"</span>: <span class="s">"On-call webhook"</span> },
<span class="k">"event"</span>: { <span class="k">"kind"</span>: <span class="s">"down"</span> | <span class="s">"up"</span> | <span class="s">"cert"</span> | <span class="s">"test"</span>, ... }
}</pre>
</div>
<h3>Event types</h3>
<p><code>down</code> — fired on the first DOWN important beat for a region, and again every <code>resend_interval</code>th consecutive down if configured.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"down"</span>,
<span class="k">"monitor"</span>: {
<span class="k">"id"</span>: <span class="s">"abc123def456"</span>,
<span class="k">"name"</span>: <span class="s">"My API"</span>,
<span class="k">"url"</span>: <span class="s">"https://api.example.com/health"</span>,
<span class="k">"region"</span>: <span class="s">"us-west"</span> <span class="c">// always present — runners default to "default" if REGION env var is unset</span>
},
<span class="k">"ping"</span>: {
<span class="k">"status_code"</span>: <span class="n">503</span>,
<span class="k">"latency_ms"</span>: <span class="n">412</span>,
<span class="k">"error"</span>: <span class="n">null</span>,
<span class="k">"checked_at"</span>: <span class="s">"2026-04-08T14:23:00.000Z"</span>
}
}</pre></div>
<p><code>up</code> — fired on recovery, only when <em>that same region</em> transitions back from DOWN. Same shape as <code>down</code>.</p>
<p><code>cert</code> — fired once per renewal cycle when the TLS leaf cert drops at or below <code>cert_alert_days</code> for a region.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"cert"</span>,
<span class="k">"monitor"</span>: { <span class="k">"id"</span>: <span class="s">"…"</span>, <span class="k">"name"</span>: <span class="s">"…"</span>, <span class="k">"url"</span>: <span class="s">"…"</span>, <span class="k">"region"</span>: <span class="s">"us-west"</span> },
<span class="k">"days"</span>: <span class="n">9</span> <span class="c">// days until certificate expires</span>
}</pre></div>
<p><code>test</code> — synthetic event from <code>POST /notifications/channels/:id/test</code>. The <code>monitor</code> object is a placeholder.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"test"</span>,
<span class="k">"monitor"</span>: { <span class="k">"id"</span>: <span class="s">"test"</span>, <span class="k">"name"</span>: <span class="s">"Test event"</span>, <span class="k">"url"</span>: <span class="s">"https://example.com"</span>, <span class="k">"region"</span>: <span class="s">""</span> }
}</pre></div>
<h3>Verifying the signature</h3>
<div class="cb">
<div class="cb-header"><span class="cb-lang">node</span></div>
<pre><span class="k">import</span> { createHmac, timingSafeEqual } <span class="k">from</span> <span class="s">"crypto"</span>;
<span class="k">function</span> verify(rawBody, headerSig, secret) {
<span class="k">const</span> expected = createHmac(<span class="s">"sha256"</span>, secret).update(rawBody).digest(<span class="s">"hex"</span>);
<span class="k">return</span> timingSafeEqual(Buffer.from(expected), Buffer.from(headerSig));
}</pre>
</div>
<p>Always verify against the <em>raw</em> request body before parsing JSON.</p>
</div>
<!-- Status Pages --> <!-- Status Pages -->
<div id="status-pages" class="section"> <div id="status-pages" class="section">
<h2>Status Pages</h2> <h2>Status Pages</h2>
@ -672,6 +579,102 @@ Content-Type: application/json
}</pre></div> }</pre></div>
</div> </div>
<!-- Reliability & alert noise -->
<div id="reliability" class="section">
<h2>Reliability &amp; alert noise</h2>
<p>PingQL doesn't immediately fire on a single failed check. A few knobs let you tune how reactive vs. how stable the alerting is.</p>
<h3>Retries before DOWN</h3>
<p>If a check fails and <code>max_retries</code> is greater than zero, the runner waits <code>retry_interval_s</code> seconds and retries up to that many times <em>before</em> recording a DOWN result. A successful retry posts a single UP ping with <code>meta.retries</code> noting how many attempts it took. This kills almost all flapping caused by transient TCP resets, brief 5xx blips, or network jitter.</p>
<h3>Important beats &amp; transitions</h3>
<p>Every check is recorded, but the <code>important</code> flag on a ping is only set when the monitor's state changes (UP↔DOWN) <em>for that region</em>. Notifications fire on important beats only — never on every routine check. State is tracked independently per region: if <code>us-west</code> goes DOWN, only a subsequent <code>us-west</code> UP clears it. <code>eu-central</code> being healthy will not silence a <code>us-west</code> outage.</p>
<h3>Resend interval</h3>
<p>For long outages, set <code>resend_interval</code> to re-fire the notification every Nth consecutive DOWN beat. With <code>resend_interval: 10</code>, a still-broken monitor produces an extra alert every 10 down checks. <code>0</code> (the default) means: alert once on the transition, then stay quiet until recovery.</p>
<h3>Cert expiry alerting</h3>
<p>For HTTPS monitors PingQL extracts the TLS leaf certificate's days-until-expiry on every check. When that drops at or below <code>cert_alert_days</code> for the first time, a separate <code>cert</code> notification fires (one per region). The flag clears when the cert is renewed, so each renewal cycle gets exactly one alert. Set <code>cert_alert_days: 0</code> to disable.</p>
<h3>Default empty query</h3>
<p>If you don't supply a <code>query</code>, the monitor is considered up only on a <strong style="color:#4ade80">2xx</strong> response. Redirects (3xx), client errors (4xx) and server errors (5xx) all count as DOWN. Use the QL if you want different behaviour.</p>
</div>
<!-- Webhook payload -->
<div id="webhook-payload" class="section">
<h2>Webhook payload</h2>
<p>Webhook channels POST a JSON body to the configured URL on every event. The HTTP method is <code>POST</code>, 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.</p>
<h3>Headers</h3>
<table>
<thead><tr><th>Header</th><th>Description</th></tr></thead>
<tbody>
<tr><td>content-type</td><td><code>application/json</code></td></tr>
<tr><td>user-agent</td><td><code>PingQL-Notifier/1</code></td></tr>
<tr><td>x-pingql-signature</td><td>Hex-encoded HMAC-SHA256 of the raw request body, keyed by <code>config.secret</code>. Only present when a secret is configured. Verify it server-side to confirm the request came from PingQL.</td></tr>
<tr><td><em>custom</em></td><td>Any headers from <code>config.headers</code> are forwarded as-is.</td></tr>
</tbody>
</table>
<h3>Body shape</h3>
<p>Every payload has the same envelope:</p>
<div class="cb">
<div class="cb-header"><span class="cb-lang">json</span></div>
<pre>{
<span class="k">"channel"</span>: { <span class="k">"id"</span>: <span class="s">"&lt;uuid&gt;"</span>, <span class="k">"name"</span>: <span class="s">"On-call webhook"</span> },
<span class="k">"event"</span>: { <span class="k">"kind"</span>: <span class="s">"down"</span> | <span class="s">"up"</span> | <span class="s">"cert"</span> | <span class="s">"test"</span>, ... }
}</pre>
</div>
<h3>Event types</h3>
<p><code>down</code> — fired on the first DOWN important beat for a region, and again every <code>resend_interval</code>th consecutive down if configured.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"down"</span>,
<span class="k">"monitor"</span>: {
<span class="k">"id"</span>: <span class="s">"abc123def456"</span>,
<span class="k">"name"</span>: <span class="s">"My API"</span>,
<span class="k">"url"</span>: <span class="s">"https://api.example.com/health"</span>,
<span class="k">"region"</span>: <span class="s">"us-west"</span> <span class="c">// always present — runners default to "default" if REGION env var is unset</span>
},
<span class="k">"ping"</span>: {
<span class="k">"status_code"</span>: <span class="n">503</span>,
<span class="k">"latency_ms"</span>: <span class="n">412</span>,
<span class="k">"error"</span>: <span class="n">null</span>,
<span class="k">"checked_at"</span>: <span class="s">"2026-04-08T14:23:00.000Z"</span>
}
}</pre></div>
<p><code>up</code> — fired on recovery, only when <em>that same region</em> transitions back from DOWN. Same shape as <code>down</code>.</p>
<p><code>cert</code> — fired once per renewal cycle when the TLS leaf cert drops at or below <code>cert_alert_days</code> for a region.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"cert"</span>,
<span class="k">"monitor"</span>: { <span class="k">"id"</span>: <span class="s">"…"</span>, <span class="k">"name"</span>: <span class="s">"…"</span>, <span class="k">"url"</span>: <span class="s">"…"</span>, <span class="k">"region"</span>: <span class="s">"us-west"</span> },
<span class="k">"days"</span>: <span class="n">9</span> <span class="c">// days until certificate expires</span>
}</pre></div>
<p><code>test</code> — synthetic event from <code>POST /notifications/channels/:id/test</code>. The <code>monitor</code> object is a placeholder.</p>
<div class="cb"><div class="cb-header"><span class="cb-lang">json — event</span></div>
<pre>{
<span class="k">"kind"</span>: <span class="s">"test"</span>,
<span class="k">"monitor"</span>: { <span class="k">"id"</span>: <span class="s">"test"</span>, <span class="k">"name"</span>: <span class="s">"Test event"</span>, <span class="k">"url"</span>: <span class="s">"https://example.com"</span>, <span class="k">"region"</span>: <span class="s">""</span> }
}</pre></div>
<h3>Verifying the signature</h3>
<div class="cb">
<div class="cb-header"><span class="cb-lang">node</span></div>
<pre><span class="k">import</span> { createHmac, timingSafeEqual } <span class="k">from</span> <span class="s">"crypto"</span>;
<span class="k">function</span> verify(rawBody, headerSig, secret) {
<span class="k">const</span> expected = createHmac(<span class="s">"sha256"</span>, secret).update(rawBody).digest(<span class="s">"hex"</span>);
<span class="k">return</span> timingSafeEqual(Buffer.from(expected), Buffer.from(headerSig));
}</pre>
</div>
<p>Always verify against the <em>raw</em> request body before parsing JSON.</p>
</div>
</main> </main>
</div> </div>