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
- Open Settings → Webhooks.
- Enter the HTTPS URL. It must use HTTPS and resolve to a public address.
- Choose the events to send (at least one).
- 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:
{"id": "…", "type": "assessment.completed", "created_at": "…", "tenant_id": "…", "data": {}}and these headers:
| Header | Value |
|---|---|
X-RevoSecure-Event | The event type |
X-RevoSecure-Delivery | The delivery id (use it to ignore duplicates) |
X-RevoSecure-Signature | t=<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.
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"])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
| Area | Events |
|---|---|
| Assessments | assessment.completed, assessment.failed |
| Findings | finding.created, finding.status_changed, finding.reopened |
| Devices | agent.enrolled, agent.offline, asset.regressed |
| Reports | report.ready |
| AI | ai.action_requested, remediation.plan_created |
| Domains | domain.verified, domain.verification_revoked, m365.domains_added |
| Customers | customer.converted, customer.archived, customer.unarchived, customer.psa_status_changed |
| Vulnerabilities | vulnerability.detected, threat_watch.matched |
| Alerts | security_event.detected |
| Patching | patching.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.