Skip to content
RevoDocs

Webhooks

Send signed event notifications to your own systems, and verify them.

Updated

On this page

Webhooks push Revo events (an assessment finished, a finding opened, a patch deployment failed) to an HTTPS endpoint you run, for automation, chat alerts or your own ticketing. Owners and admins manage them in Settings → Webhooks.

Add an endpoint

  1. Open Settings → Webhooks.
  2. Enter the HTTPS URL. It must use HTTPS and resolve to a public address.
  3. Choose the events to send (at least one).
  4. Add webhook. The signing secret (whsec_…) is shown once: store it where your receiver can read it.

Test sends a test event straight away and shows how the endpoint responded. Recent deliveries, with their response codes, are available from the API (GET /api/v1/webhooks/{id}/deliveries).

Delivery

Every delivery is a POST with a JSON body:

JSON
{"id": "…", "type": "assessment.completed", "created_at": "…", "tenant_id": "…", "data": {}}

and these headers:

HeaderValue
X-RevoSecure-EventThe event type
X-RevoSecure-DeliveryThe delivery id (use it to ignore duplicates)
X-RevoSecure-Signaturet=<unix time>,v1=<hex HMAC>

A 2xx response counts as delivered. Anything else, or no response, is retried after 1, 5, 30, 120, 360 and 720 minutes, then given up. Redirects aren't followed.

Verify signatures

v1 is HMAC_SHA256(secret, "<t>." + raw_body) in hex. Verify it against the raw request body (before parsing JSON) and reject timestamps older than five minutes.

Python
import hashlib
import hmac
import time

def verify(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts = int(parts["t"])
    if abs(time.time() - ts) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
JavaScript
import crypto from "node:crypto";

export function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const ts = Number(parts.t);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${ts}.`).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Events

AreaEvents
Assessmentsassessment.completed, assessment.failed
Findingsfinding.created, finding.status_changed, finding.reopened
Devicesagent.enrolled, agent.offline, asset.regressed
Reportsreport.ready
AIai.action_requested, remediation.plan_created
Domainsdomain.verified, domain.verification_revoked, m365.domains_added
Customerscustomer.converted, customer.archived, customer.unarchived, customer.psa_status_changed
Vulnerabilitiesvulnerability.detected, threat_watch.matched
Alertssecurity_event.detected
Patchingpatching.deployment_failed, patching.deployment_completed, patching.feature_upgrade_failed

Webhooks are independent of notifications: an endpoint gets the events it subscribes to, whatever anyone's notification settings are. asset.regressed (protection lost, an OS newly unsupported) is only sent to webhooks.