DOCS

Webhooks reference

Outbound webhook payloads, delivery headers, signature verification, and retry behavior.

HowlOps can deliver real-time event notifications to an HTTP endpoint you control. Configure a webhook notification channel under Settings > Notification channels > Webhook.

Delivery

  • Method: POST
  • Content-Type: application/json
  • Retries: failed deliveries are retried through a durable queue with exponential backoff (base 1 min, doubling each attempt, capped at 2 h), up to 3 attempts by default. After the final attempt fails, the delivery is marked failed.

Delivery headers

HeaderDescription
Content-Typeapplication/json
X-HowlOps-SignatureHMAC-SHA256 of the raw request body, hex-encoded (see below). Present when the channel has a secret.

The event and timestamp fields are carried in the JSON body, not in headers.

Payload shape

Every webhook delivery uses the same flat envelope:

json
{
  "version": "1",
  "event": "monitor.down",
  "monitor_name": "Production API",
  "url": "https://api.example.com/health",
  "details": "Connection timed out after 10s",
  "timestamp": "2026-05-13T10:00:00Z",
  "tenant_id": "uuid",
  "monitor_id": "uuid",
  "heartbeat_id": "",
  "incident_id": "uuid",
  "severity": "critical",
  "status_code": 503,
  "response_ms": 10000,
  "region": "eu-west-1",
  "monitor_type": "http",
  "tags": ["production", "api"],
  "responder": "[email protected]",
  "runbook_url": "https://runbooks.example.com/production-api"
}

affected_count is also included when a mass-outage event affects more than one check.

FieldDescription
versionPayload schema version. Currently "1".
eventOne of monitor.down, monitor.up, monitor.ssl_expiry, test.
monitor_nameDisplay name of the monitor or heartbeat.
urlChecked URL for HTTP monitors, ping://<target> for ping checks, the ping target for heartbeats.
detailsHuman-readable diagnostic string.
timestampRFC 3339 UTC timestamp of the event.
tenant_idWorkspace (tenant) UUID.
monitor_idMonitor UUID. Empty string for heartbeat-sourced events.
heartbeat_idHeartbeat UUID. Empty string for monitor-sourced events.
incident_idUUID of the associated incident, if one was opened.
severitySeverity of the event (critical, warning, info).
status_codeHTTP status code returned by the check. 0 when unknown.
response_msResponse time in milliseconds. -1 when unknown.
regionProber region that performed the check (e.g. eu-west-1).
monitor_typeMonitor type (http, ping, heartbeat, etc.).
tagsArray of tag strings attached to the monitor.
responderOn-call responder identifier, if assigned.
runbook_urlRunbook URL configured on the monitor.
affected_countNumber of affected checks for mass-outage events (omitted when <= 1).

For heartbeat events, heartbeat_id is set, monitor_id is empty, and url is ping://<target>.

Signature verification

We sign every webhook so you can verify it's genuinely from HowlOps and wasn't modified in transit. The signature travels in the X-HowlOps-Signature header. The snippet below recomputes it from the request body and compares it safely, so you don't have to write that logic yourself.

Compute the HMAC-SHA256 of the raw request body using your webhook secret as the key, hex-encoded. Compare it to the X-HowlOps-Signature header value (raw hex, no sha256= prefix).

python
import hmac
import hashlib

def verify(secret: str, body: bytes, header: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)
typescript
import crypto from "node:crypto";

function verify(secret: string, body: Buffer, header: string): boolean {
  const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

Always use a constant-time comparison to prevent timing attacks.

Event catalog

monitor.down

Fired when a monitor (or heartbeat) opens an incident (the check is failing).

json
{
  "version": "1",
  "event": "monitor.down",
  "monitor_name": "Production API",
  "url": "https://api.example.com/health",
  "details": "HTTP 503 Service Unavailable",
  "timestamp": "2026-05-13T10:00:00Z",
  "tenant_id": "uuid",
  "monitor_id": "uuid",
  "heartbeat_id": "",
  "incident_id": "uuid",
  "severity": "critical",
  "status_code": 503,
  "response_ms": 10000,
  "region": "eu-west-1",
  "monitor_type": "http",
  "tags": ["production"],
  "responder": "",
  "runbook_url": ""
}

monitor.up

Fired when a monitor recovers (the check is passing again).

json
{
  "version": "1",
  "event": "monitor.up",
  "monitor_name": "Production API",
  "url": "https://api.example.com/health",
  "details": "Recovered after 20m10s",
  "timestamp": "2026-05-13T10:20:00Z",
  "tenant_id": "uuid",
  "monitor_id": "uuid",
  "heartbeat_id": "",
  "incident_id": "uuid",
  "severity": "info",
  "status_code": 200,
  "response_ms": 142,
  "region": "eu-west-1",
  "monitor_type": "http",
  "tags": ["production"],
  "responder": "",
  "runbook_url": ""
}

monitor.ssl_expiry

Fired when a monitored endpoint's TLS certificate is approaching expiry.

json
{
  "version": "1",
  "event": "monitor.ssl_expiry",
  "monitor_name": "Production API",
  "url": "https://api.example.com/health",
  "details": "Certificate expires in 7 days",
  "timestamp": "2026-05-13T10:00:00Z",
  "tenant_id": "uuid",
  "monitor_id": "uuid",
  "heartbeat_id": "",
  "incident_id": "",
  "severity": "warning",
  "status_code": 0,
  "response_ms": -1,
  "region": "eu-west-1",
  "monitor_type": "http",
  "tags": [],
  "responder": "",
  "runbook_url": ""
}

Testing webhooks

Send a test delivery from Settings > Notification channels > select channel > Test. The test event uses event: "test" with the same flat envelope.

For local development of your own webhook receiver, forward deliveries to localhost with a tool such as Smee.io (no HowlOps affiliation).

See also

Was this page helpful?