Delivery — email & webhooks
Email to every recipient, an optional webhook, the monitor.alert payload, and the retry rule.
Livehttps://api.offendersearch.appSandboxhttps://sandbox.offendersearch.appTwo 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.
| Channel | Set it with | What arrives |
|---|---|---|
channels.email, channels.emails | One alert email per delivery run, listing every pending alert for that set of recipients. | |
| Webhook | channels.webhookUrl | One 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.
| Field | Type | Description |
|---|---|---|
| email optional | string or null | One 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 null | More recipient addresses, for a monitor whose alerts go to a team. Example ["hr@example.com","security@example.com"] |
| webhookUrl optional | string (uri) or null | The 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" |
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.
{
"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"
}// 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.
| Field | Type | Description |
|---|---|---|
| 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 | string | The 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 | string | The monitor that fired. One endpoint can serve every monitor on the account; route on this. Example "mon_b8f40d7afb9dc0d8" |
| monitorLabel | string or null | The monitor's label, or null when it has none. |
| product | string one of "sex-offender", "criminal" | The monitor's dataset. |
| detail | AlertRecord | The 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.
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.