SurfacedBySurfacedBy Docs

Console Webhooks

Register an HTTPS endpoint and receive signed scan-lifecycle events from the SurfacedBy Console API.

Ask an AI:Open in ChatGPTOpen in Claude

Console webhooks are the supported way to know when a scan finishes. Register an HTTPS endpoint once, and every eligible event is POSTed to you with a signed JSON body.

Registering an endpoint

From the Console dashboard under Webhooks, or via the API:

curl -X POST https://api.surfacedby.com/api/v1/console/webhooks \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example.com/surfacedby/hook",
    "events": ["scan.completed", "scan.failed"]
  }'

The response includes a signing_secret that you MUST store. This is the only time the secret is returned; rotate it later by creating a new endpoint and deleting the old one.

You can register up to 10 endpoints per Console account. Each endpoint can subscribe to any subset of the event types below.

Event types

EventWhen it fires
scan.completedA Console scan reached a successful terminal state. The payload includes scan id, domain, completion time, and a direct link to the scan result.
scan.failedA Console scan reached a terminal failure. Credits were refunded to your balance automatically.
scan.progressA long-running scan emits periodic progress updates. Rate-limited to at most one event per scan per 5 seconds.
report.readyA generated report (if requested via add-on) is ready for download.

Payload shape

Every webhook body has the same envelope:

{
  "id": "wh_01HW...",
  "event": "scan.completed",
  "created_at": "2026-04-17T12:34:56Z",
  "account_id": "acc_01HV...",
  "data": {
    "scan_id": "scan_01HX...",
    "domain": "yourbrand.com",
    "completed_at": "2026-04-17T12:33:40Z",
    "result_url": "https://api.surfacedby.com/api/v1/console/scans/scan_01HX..."
  }
}

The contents of data vary by event type. The envelope fields (id, event, created_at, account_id) are always present.

Signature verification

Every delivery includes an X-SurfacedBy-Signature header computed as:

sha256=<HMAC-SHA256(signing_secret, raw_request_body)>

Verify every request before trusting it. A minimal Python verifier:

import hmac, hashlib
 
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

Constant-time comparison (hmac.compare_digest) matters: a naive == is vulnerable to timing attacks.

Retry behaviour

If your endpoint responds with a non-2xx status (or times out), we retry with exponential backoff up to 8 attempts over roughly 24 hours. A delivery that permanently fails after exhausting retries is marked failed in the dashboard and can be redelivered manually from the Webhooks page.

Deliveries time out at 10 seconds. If your receiver needs longer, acknowledge the webhook quickly (a 200 response) and process the event asynchronously.

Delivery ordering

We do not guarantee strict ordering. Two events for the same scan may arrive out of order on rare retries. Use the created_at field, not receipt time, to reconstruct order if you need it.

Testing

Before pointing a webhook at production traffic, use a tool like webhook.site to inspect the live payload. Console keys with the sk_test_ prefix run scans in sandbox mode; register your endpoint on a test key first and trigger a sandbox scan to verify your signature verification and parsing end-to-end.

On this page