Caching
Reports are cached on the server by hostname and section list, so asking for the same sections again within the cache time returns the same observation with "cached": true and its original observedAt times. The Cache-Control headers listed above also let browsers and the CDN reuse a response for a short time. There is no parameter to force a fresh lookup.
CORS
The endpoints on this page send Access-Control-Allow-Origin: * and answer OPTIONS pre-flight requests, for GET only. You can call them from a page on another origin. They never read cookies, so no credentials are involved.
Fair use
Every request triggers real queries to registries, DNS resolvers and the looked-up site itself. Use the API for interactive tools, dashboards and occasional checks of domains you have a reason to look at. Do not crawl lists of domains, do not rotate addresses to get around the limits, and do not resell the output. The acceptable-use policy applies to API traffic exactly as it does to the site.
Outbound webhooks
Signed-in members can have watch alerts, expiry reminders and marketplace notices posted to a URL they own: a Slack incoming webhook or a generic https endpoint (workspace → Settings → Notification channels). This section documents what the receiver gets. It is outbound only: OrbitProbe never accepts requests on these URLs, and there is no inbound API.
Receivers must be https on a public host (default port, no credentials; private, local and reserved hosts are refused, and the host is resolved and checked again at send time). Redirects are not followed. A delivery counts as done when the receiver answers 2xx; after five failed deliveries in a row the webhook is switched off and the owner sees a notice in the workspace.
Request
| Name | Description |
|---|---|
| POST | JSON body, Content-Type: application/json; charset=utf-8, User-Agent: OrbitProbe-Webhook/1.0 |
| X-OrbitProbe-Event | The kind (see below). |
| X-OrbitProbe-Delivery | The delivery id (ntf_<number>). Repeated on a retry, so you can de-duplicate. |
| X-OrbitProbe-Signature | Generic endpoints only: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> with the signing secret shown in Settings. |
Body (generic endpoints)
| Name | Description |
|---|---|
| id | Delivery id, the same as X-OrbitProbe-Delivery. |
| kind | One of the kinds below. |
| category | watch, expiry, market or test. |
| title, text | The notification in the account’s language: the same wording the push notification carries. Counts are "N of M resolvers", never percentages. |
| url | Where to look in the workspace. |
| createdAt | When the event was queued (ISO 8601, UTC). |
| account | The recipient’s account id, so one receiver can serve several accounts. |
| data | The raw event as stored: domain, counts, listing id, amount in integer minor units and currency, and so on. Fields vary by kind; anything missing was not measured, never "no". |
{
"id": "ntf_1042",
"kind": "watch-changed",
"category": "watch",
"title": "Watch: unexpected change",
"text": "example.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.",
"url": "https://www.orbitprobe.com/app/watch/00000000-0000-4000-8000-000000000000",
"createdAt": "2026-01-01T00:00:00.000Z",
"account": "00000000-0000-4000-8000-0000000000aa",
"data": {
"domain": "example.com",
"kind": "dns-change",
"record_type": "TXT",
"name": "example.com",
"matched": 3,
"total": 12,
"failed": 0,
"differing": 9,
"watch_id": "00000000-0000-4000-8000-000000000000",
"event_id": 77
}
}Body (Slack)
Slack incoming webhooks receive {"text": "…"} only: title, text and URL on three lines, no Block Kit and no attachments, so the message renders in every Slack client and in Slack-compatible receivers such as Mattermost or Rocket.Chat.
{
"text": "*Watch: unexpected change*\nexample.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.\nhttps://www.orbitprobe.com/app/watch/…"
}Verifying the signature
Recompute the HMAC over "<t>.<raw body>" with your secret, compare in constant time, and reject timestamps older than five minutes. Do not parse and re-serialise the JSON before hashing: sign the bytes as received.
// Node.js receiver: verify X-OrbitProbe-Signature before trusting the body
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, rawBody, header, nowSeconds = Math.floor(Date.now() / 1000)) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
if (!m || Math.abs(nowSeconds - Number(m[1])) > 300) return false;
const expected = createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(m[2], 'hex'));
}Kinds
watch-changed, watch-target, watch-failed, watch-recovered, watch-partial · expiry · offer-received, offer-countered, offer-accepted, offer-rejected, offer-withdrawn, offer-expired, listing-submitted, listing-approved, listing-paused, listing-removed, agreement-cancelled · test (the "Send test" button).
Limits
At most 5 webhooks per account and 20 posts per account per hour; beyond that, events still reach the in-app inbox and e-mail but are not posted. Deliveries are retried up to five times over later runs of the five-minute job. An "offer-accepted" event records an agreement only: no payment, escrow or transfer is implied.
OpenAPI description
A machine-readable OpenAPI 3.1 document of these endpoints is generated from the same code: /api/openapi.json