Offendersearch
Monitoring API Reference

Delivery — email & webhooks

Email by default, an optional signed webhook you verify, and the retry contract behind both.

Base URL https://api.offendersearch.app

Two channels

Alerts are delivered to the channels you set on the monitor. Email is the default; a webhook is added when you set webhookUrl. You can set either or both, and at least one is required at creation.

ChannelSet it withWhat arrives
Emailchannels.emailA human-readable alert email to the address you name, one per event.
Webhookchannels.webhookUrlThe alert object as a signed JSON POST to your endpoint, one per event.

Email

Set channels.email to an address and each alert is delivered there as an email. 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 — the same alert object the history endpoint returns. Respond 2xx promptly to acknowledge. Every delivery is signed so you can verify it came from us: verify the X-Offendersearch-Signature header (an HMAC-SHA256 of the raw request body using your signing secret) before trusting a payload.

Verifying a webhook
import crypto from "node:crypto";

app.post("/hooks/os", express.raw({ type: "*/*" }), (req, res) => {
  const sent = req.get("X-Offendersearch-Signature") ?? "";
  const expected = crypto
    .createHmac("sha256", process.env.OS_WEBHOOK_SECRET)
    .update(req.body)              // the RAW body, before JSON parsing
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
    return res.sendStatus(400);
  }

  const alert = JSON.parse(req.body.toString("utf8"));
  enqueue(alert.id, alert);        // at-least-once: de-duplicate on alert.id
  res.sendStatus(200);
});

Retries

A webhook that does not answer 2xx is retried with exponential backoff for several attempts, so a brief outage on your side does not lose an alert. Treat delivery as at-least-once: an alert may arrive more than once, so de-duplicate on alert.id. An alert that cannot be delivered after every retry is still readable from GET /v1/monitors/{id}/alerts, so the history endpoint is the backstop for anything a webhook missed.

Make your handler idempotent and fast. Acknowledge with a 2xx, then do the work asynchronously. A handler that blocks risks a timeout that looks like a failure and triggers a retry.