Webhooks

Receive real-time HTTP notifications when AttackerView finds something.

How it works

When a scan completes, detects new issues, fixes, regressions, or new subdomains, AttackerView sends a signed JSON POST request to your webhook URL. You configure one URL per account in Settings.

Webhooks fire for all scan types: manual scans, API-triggered scans, retests, and Watchtower monitoring. If you paste a Slack incoming webhook URL, payloads are automatically formatted as Slack Block Kit messages.

Event types

findings.changed

Fires when a scan creates, fixes, or regresses findings on a domain. Only sent when there are actual changes.

scan.completed

Fires when a scan finishes. Includes a finding summary by severity. Useful for async CI/CD pipelines: trigger a scan via API, then listen for completion on your webhook instead of polling.

subdomain.discovered

Fires when Watchtower discovery finds new subdomains or related hosts for a monitored domain.

pentest.completed

Fires when an AI pentest session finishes, and only if this account already has webhook delivery. Includes finding count, verified count, and credit cost. Buying a one-off pentest does not grant a webhook subscription.

Payload schemas

findings.changed

{
  "eventType": "findings.changed",
  "timestamp": "2026-02-24T09:15:00.000Z",
  "domain": {
    "hostname": "example.com",
    "id": "domain-uuid"
  },
  "scanId": "scan-uuid",
  "scanType": "sentinel",
  "permalink": "https://attackerview.com/app/d/example.com?section=issues",
  "changes": {
    "new": [
      {
        "checkId": "security-headers-hsts",
        "title": "Missing HSTS header",
        "severity": "medium",
        "category": "Security Headers",
        "evidence": "No Strict-Transport-Security header found",
        "permalink": "https://attackerview.com/app/d/example.com?section=issues&checkId=security-headers-hsts"
      }
    ],
    "regressed": [
      {
        "checkId": "tls-cert-expiring",
        "title": "TLS certificate expiring soon",
        "severity": "high",
        "category": "TLS",
        "evidence": "Certificate expires in 6 days",
        "permalink": "...",
        "regressionCount": 2
      }
    ],
    "fixed": []
  }
}

scanType is one of: manual, retest, sentinel, patrol, recon. Regressed findings include regressionCount (how many times the issue has been fixed then reappeared).

scan.completed

{
  "eventType": "scan.completed",
  "timestamp": "2026-02-27T12:00:00.000Z",
  "domain": {
    "hostname": "staging.example.com",
    "id": "domain-uuid"
  },
  "scan": {
    "id": "scan-uuid",
    "status": "completed",
    "triggeredBy": "api",
    "findingSummary": {
      "total": 4,
      "critical": 0,
      "high": 1,
      "medium": 2,
      "low": 1,
      "info": 0
    }
  },
  "permalink": "https://attackerview.com/app/d/staging.example.com"
}

triggeredBy is one of: manual, api, watchtower. The findingSummary counts failing checks by severity at completion time.

subdomain.discovered

{
  "eventType": "subdomain.discovered",
  "timestamp": "2026-02-24T09:15:00.000Z",
  "domain": {
    "hostname": "example.com",
    "id": "domain-uuid"
  },
  "permalink": "https://attackerview.com/app/d/example.com?section=overview",
  "subdomains": [
    {
      "hostname": "staging.example.com",
      "discoveryMethod": "certificate_transparency"
    },
    {
      "hostname": "api.example.com",
      "discoveryMethod": "dns_expansion"
    }
  ]
}

pentest.completed

{
  "eventType": "pentest.completed",
  "timestamp": "2026-03-01T14:30:00.000Z",
  "pentest": {
    "id": "session-uuid",
    "hostname": "app.example.com",
    "status": "completed",
    "findingCount": 3,
    "verifiedCount": 2,
    "creditCost": 12
  },
  "permalink": "https://attackerview.com/app/d/app.example.com"
}

status is one of: completed, failed. findingCount is the total number of findings discovered. verifiedCount is how many were deterministically verified. creditCost is the number of credits consumed.

Verifying signatures

Every webhook request includes an X-AV-Signature-256 header containing an HMAC-SHA256 signature of the request body, using the signing key you received when setting up the webhook.

The header format is:

X-AV-Signature-256: sha256=<hex-encoded HMAC-SHA256>

Example verification in Node.js:

import { createHmac, timingSafeEqual } from "node:crypto";

function verifySignature(body, signature, secret) {
  const expected = "sha256=" +
    createHmac("sha256", secret).update(body).digest("hex");
  return timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your handler:
const sig = req.headers["x-av-signature-256"];
const raw = await req.text();
if (!verifySignature(raw, sig, process.env.AV_WEBHOOK_SECRET)) {
  return new Response("Invalid signature", { status: 401 });
}
const payload = JSON.parse(raw);

Example in Python:

import hmac, hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Retry behavior

If your endpoint returns a non-2xx status code or the request times out (10 seconds), AttackerView retries with exponential backoff:

AttemptDelay
1st retry~5 minutes
2nd retry~30 minutes
3rd retry~2 hours
4th retry~5 hours
After 5 failuresDelivery marked as failed

A small random jitter (0-60 seconds) is added to each retry delay. You can see the delivery status and retry count in Settings under the delivery log.

Slack integration

If your webhook URL is a Slack incoming webhook (hooks.slack.com), payloads are automatically formatted as Slack Block Kit messages with severity-colored sidebar, emoji prefixes, and a "View all changes" button. No extra configuration needed.

Tips

  • Your endpoint must accept POST requests with Content-Type: application/json.
  • Return a 2xx status code quickly. If your processing takes time, accept the webhook first and process asynchronously.
  • Changing your webhook URL generates a new signing key. The old key stops working immediately.
  • Use the "Send test" button in Settings to verify your endpoint is reachable and your signature verification works.
  • If you downgrade from a paid plan, pending deliveries are cancelled and the webhook is paused. Re-upgrading does not auto-enable it.