Webhooks
Webhooks deliver real-time event notifications to your application when breached passwords are detected or credential exposures are discovered.
Event types
LeakJar sends webhook events for the following scenarios:
password_protect.hitBPDFired when a password check returns a match in the breach dataset. Includes the exposure count but never the password or hash.
password_protect.blockedBPDFired when a breached password is blocked by a policy action. Useful for tracking enforcement effectiveness.
monitoring.exposure_detectedMonitoringFired when the exposure monitoring system discovers new credential exposures matching your monitored domains or user identifiers.
Webhook payload
All webhook events share a common envelope structure:
{
"id": "evt_abc123def456",
"type": "password_protect.hit",
"created_at": "2026-02-15T14:30:00Z",
"project_id": "proj_xyz789",
"data": {
"check_id": "chk_001",
"flow": "signup",
"exposure_count": 3861493,
"policy_action": "block",
"user_identifier_hash": "sha256:a1b2c3..."
}
}id — unique identifier for idempotent processing.
type — the event type string.
data — event-specific payload. Never contains plaintext passwords or full hashes.
user_identifier_hash — a one-way hash of the user identifier for correlation, not the identifier itself.
Setup instructions
- Open your project in the LeakJar Console and navigate to Settings → Webhooks.
- Add an endpoint URL — this must be an HTTPS endpoint on your server that can receive POST requests.
- Select event types you want to receive. You can subscribe to all events or pick specific ones.
- Copy the signing secret — you'll use this to verify webhook signatures.
- Send a test event to confirm your endpoint is receiving and processing events correctly.
Signature verification
Every webhook request includes a X-LeakJar-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature before processing the event to ensure the request is authentic.
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhookSignature(
body: string,
signature: string,
secret: string
): boolean {
const expected = createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
// Use timing-safe comparison to prevent timing attacks
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
// const isValid = verifyWebhookSignature(
// rawBody,
// req.headers["x-leakjar-signature"],
// process.env.LEAKJAR_WEBHOOK_SECRET
// );Handling events
Your webhook endpoint should respond with a 200 status code as quickly as possible. Process events asynchronously to avoid timeouts.
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get(
"x-leakjar-signature"
) ?? "";
if (!verifyWebhookSignature(body, signature, secret)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
// Acknowledge immediately
// Process the event asynchronously
switch (event.type) {
case "password_protect.hit":
await queue.enqueue("process-breach-hit", event.data);
break;
case "password_protect.blocked":
await queue.enqueue("log-blocked-attempt", event.data);
break;
case "monitoring.exposure_detected":
await queue.enqueue("handle-exposure", event.data);
break;
}
return new Response("OK", { status: 200 });
}