Console Webhooks
Register an HTTPS endpoint for your Console account, verify signed deliveries, and understand retries.
A webhook endpoint receives a signed HTTP POST each time an event it subscribes to occurs. Register endpoints from Webhooks in the Console or through the API. Console webhooks are not limited by plan.
An endpoint registered in the Console receives events about your Console account's domains, and only those: when a Console check completes or fails, it receives scan.completed or scan.failed for that check. Events about domains tracked in a SurfacedBy dashboard workspace never reach it, and Console endpoints do not appear in the dashboard's webhook list. Polling GET https://api.surfacedby.com/api/v1/console/scans/{scan_id} works as well; see Triggering a check.
Registering an endpoint
urlmust be anhttps://URL without a username or password.eventsis a non-empty list of event types. An unknown type returns422with the list of supported types.description(optional, up to 200 characters) andenabled(optional, defaulttrue).
The response includes a signing_secret (64 hex characters). It is returned only once: store it right away. There is no rotate endpoint; to change a secret, register a new endpoint, move your receiver to it, then delete the old one.
Event types
events accepts the event types a Console domain produces:
scan.progress,scan.completed, andscan.failedfor each check.citation.changedwhen a completed check's citation count differs from the previous check of the same domain.competitor.discoveredwhen a check finds a new competitor.
alert.triggered, domain.added, and report.ready come from dashboard features. A Console endpoint that lists them is refused with 422 validation_error, and supported_events in the response lists the accepted types.
Payload shape
Every delivery body uses the same envelope. A scan.completed for a Console check looks like this:
id, type, created_at, livemode, and data are always present. The fields inside data depend on the event type; data.workspace_id and data.workspace_slug are always included. Keys arrive sorted alphabetically, so parse the JSON rather than relying on key order.
results_url is carried by scan.completed for Console checks only. It is the absolute URL of the check's results; fetch it with your API key (see Read the results). A scan.failed event carries error_summary, one of a fixed set of customer-facing messages. Webhook events lists every field of every event.
Request headers
| Header | Value |
|---|---|
X-SurfacedBy-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
X-SurfacedBy-Timestamp | The same unix timestamp as t |
X-SurfacedBy-Event-Id | The event id (same as id in the body) |
X-SurfacedBy-Event-Type | The event type |
X-SurfacedBy-Delivery-Id | The id of this delivery |
X-SurfacedBy-Attempt | The attempt number, starting at 1 |
Signature verification
The v1 value is an HMAC-SHA256, keyed with your signing secret, over the timestamp, a period, and the raw request body: f"{t}.{raw_body}". Verify it against the raw bytes you received, before parsing the JSON.
Use a constant-time comparison such as hmac.compare_digest. Rejecting old timestamps protects you against replayed requests. See Signatures for Node.js and PHP versions.
Retries
A delivery succeeds when your endpoint answers with a 2xx status. Any other status, a connection error, or a timeout (about 10 seconds) counts as a failure, and the delivery is retried after 10 seconds, 30 seconds, 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours. After 8 failed attempts in total (roughly 8.5 hours) the delivery is abandoned.
Answer quickly with a 200 and process the event asynchronously if the work takes longer.
Delivery ordering
Ordering is not guaranteed, and a retried delivery can arrive after a later event. Use created_at in the body, not the time you received it, to order events. Use the event id to ignore duplicates.
Testing
Point an endpoint at a request inspector such as webhook.site to see the exact headers and body before wiring your receiver.