Monitoring API · v1.0.0

Delivery — email & webhooks

Email to every recipient, an optional webhook, the monitor.alert payload, and the retry rule.

Livehttps://api.offendersearch.appSandboxhttps://sandbox.offendersearch.app

Two channels

Alerts are delivered to the channels you set on the monitor: email, a webhook, or both. A monitor needs at least one email address or a webhookUrl at creation. The account owner’s email receives the alert email in addition to the addresses you set.

ChannelSet it withWhat arrives
Emailchannels.email, channels.emailsOne alert email per delivery run, listing every pending alert for that set of recipients.
Webhookchannels.webhookUrlOne JSON POST per alert — the monitor.alert payload below.

The Channels object — alert delivery request fields

Where a monitor's alerts are delivered: channels on POST /v1/monitors. The account owner's email always receives the alert email as well.

Returned or accepted by POST /v1/monitors.

FieldTypeDescription
email
optional
string or nullOne recipient address. Merged with emails into one de-duplicated list, returned on the monitor as channels.emails.
Example "alerts@example.com"
emails
optional
array of string or nullMore recipient addresses, for a monitor whose alerts go to a team.
Example ["hr@example.com","security@example.com"]
webhookUrl
optional
string (uri) or nullThe URL that receives each alert as a JSON POST — the monitor.alert webhook payload. Must be an https:// URL on a public host name or public IP; anything else (a malformed URL, http://, localhost, a private, loopback or link-local address) is refused with 422. Redirects are not followed.
Example "https://example.com/hooks/offendersearch"

Email

Every address in channels.email and channels.emails, plus the account owner, receives the alert email. Alerts for monitors with the same recipients are sent together, one email per run; each line gives the event, the monitor’s label (or the matched name), the city and jurisdiction, and the date. The email ends with the notice that alerts are not a consumer report. Email is the right channel for a person who reads the alerts; a webhook is the right channel for a system that acts on them.

Signed webhooks

Set channels.webhookUrl and each alert is POSTed to that URL as JSON, with Content-Type: application/json and User-Agent: Offendersearch-Monitor/1. The body is the monitor.alert payload, defined below; its detail is the same record summary the alert history returns as record.

A signed delivery carries X-Offendersearch-Signature: sha256=<hex>, the HMAC-SHA256 of the raw request body. Deliveries are not signed today, and no signing secret is issued to accounts, so do not reject a delivery for lacking the header. Authenticate deliveries with a secret of your own instead: put a long random token in the webhookUrl (it is stored with the monitor and is only ever returned to your account), and refuse any POST that does not carry it.

POST to your webhookUrl
{
  "type": "monitor.alert",
  "event": "new",
  "alertId": "hit_7fa176c8889c7569",
  "monitorId": "mon_b8f40d7afb9dc0d8",
  "monitorLabel": "Jordan Rivera — SO watch",
  "product": "sex-offender",
  "detail": {
    "name": "JORDAN RIVERA",
    "jurisdiction": "TX",
    "locality": "Houston",
    "matchLabel": "dob_match",
    "matchConfidence": 1.0,
    "personRef": "prs_3c9e1f0a7b2d4e6f8a1b",
    "recordId": "rec_ab12cd34ef56gh78ij90"
  },
  "occurredAt": "2026-09-06T12:00:41+00:00",
  "sentAt": "2026-09-06T12:01:03+00:00"
}
Authenticating a delivery
// channels.webhookUrl: "https://example.com/hooks/os?token=<a long random secret>"
import crypto from "node:crypto";

app.post("/hooks/os", express.json(), (req, res) => {
  const sent = String(req.query.token ?? "");
  const expected = process.env.OS_HOOK_TOKEN;   // the secret you put in the webhookUrl
  const ok = sent.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected));
  if (!ok) return res.sendStatus(404);

  const alert = req.body;                       // type: "monitor.alert"
  enqueue(alert.alertId, alert);                // one alert per person per registry: de-duplicate on alertId
  res.sendStatus(200);                          // answer within 10 seconds
});

The monitor.alert webhook payload object

The JSON body POSTed to channels.webhookUrl, one request per alert, with Content-Type: application/json and User-Agent: Offendersearch-Monitor/1. A signed delivery also carries X-Offendersearch-Signature: sha256=<hex>, the HMAC-SHA256 of the raw body; deliveries are not signed today, so authenticate them with a secret token in your webhookUrl.

FieldTypeDescription
type
string
one of "monitor.alert"
Always monitor.alert.
event
string
one of "new", "changed", "removed"
The event: new, changed or removed. Same meaning as the alert's eventType.
alertId
stringThe id of the alert this delivery reports — hit_ and 16 hex characters, the same value GET /v1/monitors/{id}/alerts lists. One alert per monitor and person per registry, so de-duplicate deliveries on it.
Example "hit_7fa176c8889c7569"
monitorId
stringThe monitor that fired. One endpoint can serve every monitor on the account; route on this.
Example "mon_b8f40d7afb9dc0d8"
monitorLabel
string or nullThe monitor's label, or null when it has none.
product
string
one of "sex-offender", "criminal"
The monitor's dataset.
detail
AlertRecordThe matched record, summarised, with the same personRef and recordId the alert's record carries.
occurredAt
string (date-time)When this record first matched the monitor (the alert's firstSeenAt).
sentAt
string (date-time)When this delivery was sent.

Retries

Each pending alert is POSTed once per delivery run, with a 10-second timeout. An alert is marked delivered — its notifiedAt is set — as soon as either its email or its webhook POST succeeds (a 2xx answer), and it is not sent again. When neither channel succeeds, the alert stays pending and both are tried again on the next run, the following day. There is no faster retry and no backoff schedule. Every alert is readable from GET /v1/monitors/{id}/alerts whatever happened to its delivery, so the history endpoint is the record to reconcile against.

Answer fast. Acknowledge with a 2xx inside 10 seconds, then do the work asynchronously. On a monitor with email and a webhook, a delivered email marks the alert delivered even when the webhook POST failed — read the alert history to catch anything your endpoint missed.

Product overview