Developer reference

Webhook signing and verification

Every outbound alert delivered to your endpoint carries an HMAC-SHA256 signature derived from the raw request body and your webhook secret. Verify the signature on every request; reject any request whose signature does not match.

The request

Breachtide sends a single POST with two request headers that carry the signing material:

X-Breachtide-Signaturestring
The value sha256=<hex digest>, where the digest is HMAC-SHA256(secret, raw_body).
Content-Typestring
Always application/json.

Payload shape

A representative JSON body:

POST /your-endpointContent-Type: application/json
{
  "event": "alert.created",
  "severity": "critical",
  "address": "[email protected]",
  "source": "combolist",
  "summary": "new plaintext password in circulation",
  "delta": {
    "previous_count": 3,
    "new_count": 4,
    "new_source_dbs": ["ComboList2026Q2"],
    "newly_exposed_fields": ["password"],
    "has_new_plaintext": true,
    "is_first_scan": false
  },
  "observed_at": "2026-05-07T14:22:08Z",
  "dashboard_url": "https://breachtide.com/dashboard/emails/me_0193..."
}

Top-level fields

eventenum
The event name. Reserved for future event types; today only one value is sent.alert.created
severityenum
How urgent the change is. Critical means rotate now.criticalwarninginfo
addressstring
The verified email address whose state changed.
sourceenum
Which feed produced the change.archivecombolistindexenriched
summarystring
A short human-readable description of the change.
deltaobject
Structured detail of what changed since the prior sweep. See the next table.
observed_atstring
ISO-8601 UTC timestamp of when the change was recorded.
dashboard_urlstring
Link to the affected email address in your Breachtide dashboard.

Delta fields

previous_countinteger
Number of distinct source databases on the prior sweep.
new_countinteger
Number of distinct source databases on the current sweep.
new_source_dbsstring[]
The named breaches or combolists that appeared since the previous sweep.
newly_exposed_fieldsenum[]
Newly exposed value categories.passwordhashusernamenamedobaddressphoneip
has_new_plaintextboolean
True when this delta introduced a plaintext password that was not present before.
is_first_scanboolean
True when this is the first sweep for the address; counts represent the initial state.

Verification

Compute HMAC-SHA256(secret, raw_body), prefix the hex digest with sha256=, and compare against the header in constant time. Constant-time comparison defends against timing-side-channel attacks.

Node.js (Express)

verify-webhook.jsNode 20+
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.BREACHTIDE_WEBHOOK_SECRET;

app.post(
  '/breachtide-webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const received = req.header('x-breachtide-signature') ?? '';
    const expected =
      'sha256=' +
      crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');

    const a = Buffer.from(received);
    const b = Buffer.from(expected);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).send('invalid signature');
    }

    const event = JSON.parse(req.body.toString('utf8'));
    handleAlert(event);
    res.status(204).end();
  },
);

Python (Flask)

verify_webhook.pyPython 3.10+
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["BREACHTIDE_WEBHOOK_SECRET"].encode()

@app.post("/breachtide-webhook")
def webhook():
    received = request.headers.get("X-Breachtide-Signature", "")
    expected = "sha256=" + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(received, expected):
        abort(401)
    handle_alert(request.get_json())
    return "", 204

Go

main.goGo 1.21+
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "os"
)

var secret = []byte(os.Getenv("BREACHTIDE_WEBHOOK_SECRET"))

func webhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    mac := hmac.New(sha256.New, secret)
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    received := r.Header.Get("X-Breachtide-Signature")
    if !hmac.Equal([]byte(received), []byte(expected)) {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return
    }
    w.WriteHeader(http.StatusNoContent)
}

Operational notes

Webhook delivery is part of Pro.

Set a URL, generate a secret, and verify with the snippets above.

See pricing