Alerts

Set up email and webhook alerts (including Slack, Teams and Feishu), see the real webhook payloads, and verify signatures.


OpenBait sends an email or webhook when a threat is detected or confirmed, when a takedown case changes status, and when a canary fires. You set it all up under Settings > Notifications in the console.

Email alerts

  1. Go to Settings > Notifications
  2. Under Recipients, add the addresses that should get alerts
  3. Choose which kinds of alerts to receive

If you don't add a recipient, alerts go to the email addresses of all the organization's owners.

Webhooks

  1. In the webhook section of Settings > Notifications, click Add Webhook
  2. Enter a name and the destination URL
  3. Choose a Format: Standard JSON, Slack, Feishu (Lark) or Microsoft Teams

With Slack, Teams, or Feishu, we send the message in a shape that service's incoming webhook can display as is. Choose Standard JSON if your own system receives it.

Your plan sets how many webhooks you can add: Free has none, Pro 1, Business 5, and Enterprise unlimited.

Event types

EventWhen it fires
threat.detectedA new threat was detected
threat.confirmedA threat was confirmed (including confirmation by the brand)
takedown.status_changedA takedown case changed status
honeytoken.triggeredA canary (honeytoken) fired

Standard format payloads

Every event has three keys: event, timestamp (UTC, RFC 3339) and data. What is inside data depends on the event.

threat.detected / threat.confirmed:

json
{
  "event": "threat.detected",
  "timestamp": "2026-09-20T03:15:00Z",
  "data": {
    "threatId": "d3a9k2f05n8u7r1m4qv0",
    "domain": "example-brand-login[.]com",
    "brand": "Example Brand",
    "threatType": "phishing",
    "severity": "high",
    "score": 82,
    "confirmed": false,
    "scanType": "public-report",
    "url": "https://openbait.com/console/threats/d3a9k2f05n8u7r1m4qv0"
  }
}

severity is one of low, medium, high or critical.

takedown.status_changed:

json
{
  "event": "takedown.status_changed",
  "timestamp": "2026-09-20T06:12:00Z",
  "data": {
    "caseId": "d3a9m7f05n8u7r1m4r20",
    "domain": "example-brand-login[.]com",
    "brand": "Example Brand",
    "status": "down",
    "url": "https://openbait.com/console/takedown/d3a9m7f05n8u7r1m4r20"
  }
}

status is one of opened, down, resolved, re-emerged, blocked, watching, domain-removed or awaiting_confirmation.

  • watching: the site is offline, and we keep checking it until the domain is removed.
  • domain-removed: the registry has removed the domain.
  • awaiting_confirmation: a report from a visitor is waiting for your answer.
  • blocked: the server still answers but goes silent for that one domain name. We can't call it down or back up, so it gets its own status.

honeytoken.triggered:

json
{
  "event": "honeytoken.triggered",
  "timestamp": "2026-09-20T09:41:00Z",
  "data": {
    "honeytokenId": "d3a9p1f05n8u7r1m4s50",
    "honeytokenName": "login-page",
    "honeytokenType": "BEACON",
    "sourceIp": "203.0.113.24",
    "userAgent": "Mozilla/5.0 ...",
    "threatLevel": "high",
    "riskScore": 75,
    "refererDomain": "example-brand-login[.]com",
    "referer": "hxxps://example-brand-login[.]com/signin"
  }
}

refererDomain, referer and internalNote are included only when they have a value.

Domains and URLs in the payload are defanged to example[.]com and hxxps://, so nobody clicks them by accident and chat tools don't unfurl a preview. If your system matches on them, turn them back into their original form.

Verifying signatures

Every request to a webhook with a secret carries these headers:

HeaderContent
X-Webhook-TimestampTime sent (UNIX seconds)
X-Webhook-Signaturesha256= followed by the HMAC-SHA256 hex string

We sign timestamp + "." + request body, using the body bytes exactly as received.

js
import crypto from "node:crypto"
 
function verify(secret, timestamp, rawBody, signatureHeader) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex")
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}

To prevent replays, reject any request whose X-Webhook-Timestamp is far from the current time. Requests use the User-Agent OpenBait-Webhook/1.0.