Webhooks
Get a signed JSON POST to a URL of your choice every
time a scan of a host you registered completes. Useful
for CI integrations, status-page updates, or pushing
grade changes into your own dashboards.
Setup
Sign in, open your
account, scroll to Scan webhooks, fill in
the host you want to subscribe to + the receiver URL,
and submit. We mint a 32-byte HMAC signing secret and
show it to you exactly once — copy it into your
receiver's secret store.
Payload shape
On every completed scan we POST a JSON body that looks
like this:
POST https://your-receiver.example/agent-disco
Content-Type: application/json
X-Agent-Disco-Event: scan.completed
X-Agent-Disco-Webhook-Id: 019d...
X-Agent-Disco-Signature: sha256=...
{
"event": "scan.completed",
"scan": {
"id": "019d...",
"host": "your-site.example",
"grade": "B",
"score": 72,
"completedAt": "2026-04-25T10:00:00+00:00",
"statusUrl": "/api/v1/scans/019d...",
"resultUrl": "/report/your-site.example"
}
}
Verifying signatures
Every payload carries an X-Agent-Disco-Signature
header of the form sha256=<hex>.
Recompute HMAC-SHA256(secret, raw_body)
and compare in constant time:
# Python
import hmac, hashlib
def verify(body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
Retries + auto-pause
We expect a 2xx response within 10 seconds. Anything
else (4xx, 5xx, timeout, DNS failure) is a delivery
failure that retries 3× with exponential backoff. After
5 consecutive terminal failures we auto-pause the
webhook — the row stays in your account but no further
deliveries fire. Resume by deleting + re-creating it,
or by ensuring the next attempt succeeds (the counter
resets on any 2xx).
Replay protection
The scan.id in every payload is a UUIDv7
— its embedded timestamp lets your receiver reject
replays older than a window you choose (we recommend
5 minutes). The signature alone doesn't include a
nonce; if you need stricter replay protection,
discard payloads whose scan.completedAt
is older than your tolerance.