Skip to content

Webhooks

Webhooks turn any HTTPS endpoint into an alert destination — pipe alerts into PagerDuty, Opsgenie, your own incident tooling, a data pipeline, or anything else that can accept a POST. Add the endpoint once and it becomes a selectable channel in every alert rule.

  1. In Integrations, find the Webhook card.

  2. Enter a label, the HTTPS URL, and (recommended) a signing secret — any string you generate. With a secret set, every delivery carries an HMAC signature your endpoint can verify.

  3. Click Add Webhook, then select it as a channel in the alert rules that should deliver to it.

Each alert is one POST with Content-Type: application/json and user agent Monita-Alerts/1.0:

{
"ruleId": "alr_9f2c41d0",
"orgId": "org_2caccd11",
"domainId": "dom_71b3a9e2",
"severity": "high",
"type": "tag_volume_change",
"title": "Meta Pixel purchase volume drop",
"body": "Alert \"Meta Pixel purchase volume drop\" matched event \"purchase\" on www.example.com/checkout (vendor Facebook (Meta Pixel))",
"impactValue": 12500,
"context": {
"event_name": "purchase",
"vendor": "Facebook (Meta Pixel)",
"url": "https://www.example.com/checkout",
"host": "www.example.com",
"path": "/checkout",
"system": "google_tag_manager"
},
"firedAt": "2026-08-13T09:30:00.000Z"
}
Field Meaning
ruleId / orgId / domainId The alert rule, organisation and property the alert belongs to. domainId is absent for org-wide rules.
severity low, medium, high or critical — or recovered on the matching recovery delivery.
type The alert type, e.g. tag_volume_change, data_validation, journey.
title / body Human-readable summary — the same text Slack and email receive.
impactValue The rule’s configured value protected, in dollars. Absent when not set.
context The matched event’s locus: event name, vendor, URL, host, path and source system. Fields vary by alert type — treat all of them as optional.
firedAt ISO 8601 timestamp of when the alert fired.

Recoveries are delivered to the same endpoint with the same shape and severity: "recovered", so your receiver can close whatever it opened.

When the integration has a signing secret, Monita sends an x-monita-signature header: the lowercase-hex HMAC-SHA256 of the raw request body, keyed with your secret. Verify it before trusting a delivery:

import { createHmac, timingSafeEqual } from 'node:crypto';
function isFromMonita(rawBody, signatureHeader, secret) {
// rawBody must be the exact bytes received — verify before any JSON parsing.
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
return (
signatureHeader?.length === expected.length &&
timingSafeEqual(Buffer.from(signatureHeader, 'hex'), Buffer.from(expected, 'hex'))
);
}

The same recipe works anywhere: HMAC_SHA256(secret, raw_body), hex-encoded, compared with a constant-time comparison. Compute it over the raw body exactly as received — re-serialising the parsed JSON can reorder keys and break the match.

Return any 2xx status quickly — do your processing after acknowledging, not before. Non-2xx responses and timeouts count as failed deliveries; Monita retries failed deliveries a limited number of times, so make your handler idempotent (the ruleId + firedAt pair identifies a delivery).