DEV Community

Daniel Root
Daniel Root

Posted on Originally published at pingwhen.app

Let an AI agent set up uptime and change alerts with one API call

AI agents are good at fetching a page once and reasoning about it. They are less good at the boring middle: a durable schedule, a paid subscription, and a signed delivery channel that keeps working after the chat ends. That is the gap we built PingWhen's agent API for.

If your agent can read a short doc, POST JSON, and hand a human a checkout link, it can set up page-change and uptime alerts without scraping, cron, or a free-tier dashboard.

What the agent needs to know first

Two public docs are enough:

There is no free plan and no agent-only price. Agents pay the same Stripe subscription as humans: Starter $9/mo (10 watches, every 15 min) or Plus $19/mo (40 watches, every 5 min).

The flow in five steps

  1. Read /llms.txt or /for-agents.
  2. POST /v1/watches with the URL to watch and where to send alerts.
  3. Give the human the returned checkout_url (or open it in an approved browser tool).
  4. Poll GET /v1/watches/:id?token=... until status is active.
  5. Receive signed webhook POSTs when the page changes, goes down, or comes back.

Payment is required before polling starts. The create call inserts the watch as pending_payment, opens Stripe Checkout, and returns immediately.

Create a watch

curl -sS -X POST https://pingwhen.app/v1/watches \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://example.com/status",
    "notify": "https://agent.example/hooks/pingwhen",
    "events": ["change", "down", "up"],
    "plan": "starter",
    "success_url": "https://agent.example/paid",
    "cancel_url": "https://agent.example/abandoned"
  }'
Enter fullscreen mode Exit fullscreen mode

notify may be an HTTPS webhook URL (preferred for agents), an email, or a US/CA phone number. events is any subset of change, down, and up. plan is starter or plus. success_url and cancel_url are optional Stripe return URLs.

A typical 201 response looks like:

{
  "watch_id": "w_...",
  "status": "pending_payment",
  "manage_token": "mt_...",
  "plan": "starter",
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_...",
  "poll": "GET /v1/watches/w_...?token=mt_...",
  "cancel": "DELETE /v1/watches/w_...?token=mt_...",
  "instructions": "Open checkout_url in a browser. Then poll until status=active."
}
Enter fullscreen mode Exit fullscreen mode

Store manage_token. You need it to poll, cancel, and verify webhook signatures. If notify is an email for an existing customer who still has spare quota, Checkout is skipped and status comes back active right away.

Poll until active

curl -sS "https://pingwhen.app/v1/watches/w_...?token=mt_..."
Enter fullscreen mode Exit fullscreen mode

Status values: pending_payment | active | past_due | canceled | expired. Once active, the JSON includes a Stripe Customer Portal URL so the human can manage billing.

Verify webhook signatures

Alerts to an HTTPS notify URL are JSON POSTs:

{
  "event": "change",
  "watch_id": "w_...",
  "url": "https://example.com/status",
  "checked_at": "2026-09-20T20:04:00Z",
  "http_status": 200,
  "excerpt": "text now includes $19/mo"
}
Enter fullscreen mode Exit fullscreen mode

event is one of change | down | up | active. Headers:

  • User-Agent: PingWhenBot/1.0
  • X-PingWhen-Signature: sha256=<hex>
  • X-PingWhen-Event: change (same as the body event)

The signature is HMAC-SHA256 of the raw request body, using manage_token as the secret. Header format is exactly sha256= plus the hex digest.

Node:

const crypto = require("crypto");

function verifyPingWhen(rawBody, signatureHeader, manageToken) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", manageToken).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Enter fullscreen mode Exit fullscreen mode

Python:

import hashlib, hmac

def verify_pingwhen(raw_body: bytes, signature_header: str, manage_token: str) -> bool:
    digest = hmac.new(manage_token.encode(), raw_body, hashlib.sha256).hexdigest()
    expected = "sha256=" + digest
    return hmac.compare_digest(expected, signature_header or "")
Enter fullscreen mode Exit fullscreen mode

Use the raw bytes you received, not a re-serialized JSON object. Respond with 410 Gone (or {"ok":false,"unsubscribe":true}) to delete the watch. We retry failed deliveries a couple of times before giving up.

Honest limits (read these before you wire it in)

  • We watch the HTML we receive: visible text after scripts/styles are stripped. We do not run a browser or execute JavaScript. If the words you care about are missing from view-source, use a JS-capable monitor or watch the JSON API the page loads instead.
  • No free plan. Starter is $9/mo for 10 watches every 15 minutes; Plus is $19/mo for 40 watches every 5 minutes.
  • Watch URLs go through SSRF protections (public HTTP(S) only). Private/local targets are refused.
  • The first check is a silent baseline. You get alerted on later changes, not on the initial snapshot.
  • Webhook destinations must be HTTPS. Email and US/CA SMS also work if that fits the human better than a hook.

Not sure whether a URL is a good candidate? The free one-shot page check fetches it the same way our watcher would and shows status, size, and a text preview.

Where this fits in an agent loop

A practical pattern: the human asks the agent to "watch this status page and ping my webhook if it dies or the copy changes." The agent reads /for-agents, creates the watch, returns the checkout link, polls to confirm payment, and then treats signed webhooks as durable events. Cancel a single watch with DELETE /v1/watches/:id?token=...; cancel the subscription through the portal URL once the watch is active.

We built PingWhen for the simple case — public, server-rendered pages and JSON URLs — not as a general browser automation platform. If that matches what your agent needs, start at /for-agents or the homepage at pingwhen.app.

Originally published on the PingWhen blog. Written with AI assistance for PingWhen, the page-change and uptime alert tool we make.

Top comments (0)