PingThatSign inStart monitoring

Documentation

How PingThat checks your sites, decides something is down, and talks to your systems.

Get started

  • Create an account and confirm your email.
  • Add a monitor: an address, how often to check, and what counts as healthy.
  • Run the first check. You'll see real timings, not sample data.
  • Add an email or webhook destination, confirm it, send a test, then create a route.

How checks work

Each check is an HTTP GET from PingThat's servers with the user agent PingThat-Monitor. We record name lookup, connection, TLS handshake, time to first byte, and total time. The response body is read only when you set a keyword rule, limited to the first 1 MiB, and never stored.

  • Status codes you list (for example 200-299, 301) count as up. Redirects are followed unless their code is one you accepted.
  • HTTPS certificates are always verified.
  • Only public internet addresses on ports 80, 443, 8080 and 8443 can be checked. Private, loopback, link-local and internal addresses are refused, including after redirects.
  • Response time means the HTTP request, not a full browser page load.

When PingThat decides something is down

Each monitor uses one of two check types.

Basic (the default)

  • One region checks on your schedule. The first failed check marks the monitor Confirming.
  • If the next check also fails, an incident opens and the monitor is Down. The number of failures in a row is shown in the incident.
  • Two consecutive passing checks resolve the incident.

Confirmed (optional)

Available when the service runs at least three check regions.

  • A failed check from the primary region marks the monitor Confirming.
  • Two other regions run fresh checks. If at least two of the three fail within 30 seconds, an incident opens.
  • If only the primary region failed, the monitor is Degraded and no incident or alert is created.
  • If confirmation can't complete, it stays unconfirmed. PingThat never quietly marks it up.
  • Recovery needs two consecutive passes from the primary region plus one pass from a different region.

Both types

  • Slow responses are tracked separately: three checks in a row over your threshold open a latency incident.
  • If checks stop arriving, the monitor shows No data, never a false Down.

Outage durations run from the first failed check to the first passing check, so they are estimates; the real start and end fall somewhere between checks.

Webhooks

PingThat sends a signed POST with a JSON body. Respond with any 2xx within 10 seconds. Other responses are retried with growing delays for a limited number of attempts, except 4xx errors (other than 408, 425 and 429), which are treated as final. Redirects are not followed. Delivery is at least once, so de-duplicate on the event id.

Headers

  • PingThat-Event-Id: stable across retries
  • PingThat-Delivery-Id: the delivery record
  • PingThat-Timestamp: unix seconds at send time
  • PingThat-Signature: v1= followed by the hex HMAC-SHA256 of timestamp.body using your signing secret

Verify the signature (Node.js)

import crypto from "node:crypto";

export function verify(secret, headers, rawBody) {
  const ts = Number(headers["pingthat-timestamp"]);
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false;        // reject replays
  const expected = "v1=" + crypto.createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`).digest("hex");
  const given = headers["pingthat-signature"] ?? "";
  return given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

Verify the signature (Python)

import hmac, hashlib, time

def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
    ts = int(headers["PingThat-Timestamp"])
    if abs(time.time() - ts) > 300:                    # reject replays
        return False
    expected = "v1=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, headers.get("PingThat-Signature", ""))

Example payload

{
  "id": "6d0c2f0e-…",                      // stable event id: use it to de-duplicate
  "type": "incident.opened",
  "created_at": "2026-10-10T14:02:19+00:00",
  "data": {
    "monitor": { "id": "…", "name": "Acme homepage", "target": "https://acme.example/" },
    "client": { "id": "…", "name": "Acme Bakery" },
    "incident": { "id": "…", "category": "availability", "failure_category": "http_status",
                  "first_failure_at": "…", "confirmed_at": "…", "url": "https://…/incidents/…" },
    "regions": [ { "region": "…", "outcome": "failure", "status_code": 503 } ]
  }
}

Event types: incident.opened, incident.resolved, incident.flapping (a summary sent instead of repeated alerts when a monitor keeps failing and recovering), and test.notification. Your signing secret is shown once when you add the webhook; you can replace it at any time.

Limits and what's not here yet

Published check IP addresses, an API for managing monitors, status pages, and reports are not available yet.