Alerts & Incidents

Webhooks

Receive signed JSON from SutramX for incidents, recoveries, expiry reminders and maintenance. Payload reference, signature verification, retries and testing.

A webhook sends every SutramX alert to your own HTTPS endpoint as a signed JSON POST. Use it to open tickets, trigger automation, feed a chat bot or log incidents in your own systems. A Zapier connection uses the same incident payload, so you can reach thousands of other apps without writing code.

Webhooks (and Zapier) depend on your plan. See Plans & limits.

Set up a webhook#

  1. Go to Alerts → Channels & API in the sidebar.
  2. On the Webhook card, click Connect Webhook.
  3. Enter your Endpoint URL, for example https://example.com/webhooks/sutramx.
  4. Click Connect.
  5. Copy the signing secret from the dialog that opens and store it securely. It is shown only once.

When you connect in the dashboard, SutramX sends a signed test event right away. Only the workspace owner can connect, edit or remove webhooks in the dashboard. Through the API, a key with the Automation access level can also create, edit and remove them, but only the owner can rotate the signing secret (see REST API access levels).

FieldWhat it doesRules
Endpoint URLWhere SutramX sends eventsMust start with https://, resolve to a public internet address and contain no username or password
NameLabel for the connectionNamed automatically; change it later with Edit, up to 60 characters

You can add up to 10 webhook connections per workspace, each with its own URL, secret and routing.

The request#

Every delivery is a single HTTP request:

http
POST /webhooks/sutramx HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: SutramX-Webhooks/1.0
X-SutramX-Event: monitor.down
X-SutramX-Delivery: 3f2b6a8e-9a51-4c1e-8d0f-2a7e5c9b1d44
X-SutramX-Timestamp: 1790932443
X-SutramX-Signature: sha256=8c0e5d2f...

{"event":"monitor.down","title":"Checkout API is DOWN",...}
HeaderContents
X-SutramX-EventThe event name (also in the event field of the body)
X-SutramX-DeliveryA unique ID for this delivery. Retries reuse the same ID
X-SutramX-TimestampUnix time in seconds when the request was signed
X-SutramX-Signaturesha256= followed by the hex HMAC-SHA256 signature

Events#

EventSent when
monitor.downA monitor went down (an incident opened), or an escalation step targets the webhook
monitor.upThe monitor recovered (the incident resolved automatically)
incident.resolvedSomeone resolved the incident manually; the monitor has not been confirmed back up
ssl.expiringAn SSL certificate is close to expiry or recently expired
domain.expiringA domain registration is close to expiry or recently expired
maintenance.startedA maintenance window started (alerts are paused)
maintenance.endedA maintenance window ended
site_health.issuesA website health crawl found new problems, Lighthouse scores dropped, or the crawl couldn't finish (for sites with issue notifications on)
third_party.incidentA followed third-party provider reported an incident or degraded component
third_party.resolvedThat provider resolved the incident or the component recovered
third_party.maintenanceThat provider started or completed maintenance (if you follow its maintenance)
last_mile.unreachableChecks from one or more Indian ISP networks keep failing while every data-centre region reaches the monitor. No incident is opened
last_mile.recoveredThose ISP networks reach the monitor again
testYou clicked Send test (or just connected)

Incident, expiry and maintenance events follow the connection's routing: expiry and monitor- or group-scoped maintenance events only go to connections whose routing covers an affected monitor. Website health, third-party and last-mile events go to every webhook connection. Escalation steps ignore routing (see below).

Incident payloads#

monitor.down, monitor.up, incident.resolved and test share one schema:

FieldTypeDescription
eventstringmonitor.down, monitor.up, incident.resolved or test
titlestring"<monitor> is DOWN", "<monitor> has RECOVERED" or "<monitor>: incident resolved manually"
monitor_namestringThe monitor's name
monitor_urlstringThe monitored URL or host
incident_idstringThe incident's ID; the same for the down and up events of one incident
regionstring or nullRegion that reported the failure (down events)
started_atstring or nullISO 8601 time the incident started (down, up and resolved events)
duration_secondsnumber or nullTotal downtime in seconds (up and resolved events)
resolutionstring or nullrecovered (monitor.up), manual (incident.resolved), null otherwise
reasonstring or nullFailure reason (down events)
incident_urlstring or nullLink to the incident in the dashboard
explanationstring or nullThe one-line "why this alert" verdict (down events). null on other events or when no verdict is available. See Why this alert

monitor.down#

json
{
  "event": "monitor.down",
  "title": "Checkout API is DOWN",
  "monitor_name": "Checkout API",
  "monitor_url": "https://api.example.com/health",
  "incident_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "region": "FRA1 (Frankfurt, Germany)",
  "started_at": "2026-10-02T09:14:03.120Z",
  "duration_seconds": null,
  "reason": "<failure reason>",
  "resolution": null,
  "incident_url": "https://app.sutramx.com/dashboard/incidents/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "explanation": "Down from 2 of 3 regions: HTTP 503 server error (Arizona still up)"
}

monitor.up#

json
{
  "event": "monitor.up",
  "title": "Checkout API has RECOVERED",
  "monitor_name": "Checkout API",
  "monitor_url": "https://api.example.com/health",
  "incident_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "region": null,
  "started_at": "2026-10-02T09:14:03.120Z",
  "duration_seconds": 252,
  "reason": null,
  "resolution": "recovered",
  "incident_url": "https://app.sutramx.com/dashboard/incidents/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "explanation": null
}

incident.resolved#

Sent instead of monitor.up when someone resolves an incident by hand while the monitor may still be failing. The payload has the same fields as monitor.up, with "event": "incident.resolved", "resolution": "manual" and the title "<monitor>: incident resolved manually". If you close tickets on monitor.up, close them on incident.resolved too.

Escalation steps#

When an escalation policy step targets webhooks, every webhook connection receives another monitor.down for the same incident_id, with reason set to "Escalation step 2: still down" (with the step number) and started_at set to when the incident started.

test#

json
{
  "event": "test",
  "title": "SutramX test alert is DOWN",
  "monitor_name": "SutramX test alert",
  "monitor_url": "https://example.com",
  "incident_id": "0b6d5d1e-3f61-4f5e-9a3c-7d1f2e8a9b10",
  "region": null,
  "started_at": "2026-10-02T09:20:00.000Z",
  "duration_seconds": null,
  "reason": "This is a test message for your Webhook integration. No action needed.",
  "resolution": null,
  "incident_url": null,
  "explanation": null
}

Notice payloads#

Notice events carry their own fields plus event, title and url (a link to the details).

ssl.expiring and domain.expiring#

json
{
  "monitor_id": "2d1c7a52-8f0e-4c3b-9b7a-5e6f1a2b3c4d",
  "monitor_name": "Checkout API",
  "monitor_url": "https://api.example.com/health",
  "kind": "ssl",
  "expires_at": "2026-10-09T23:59:59.000Z",
  "days_remaining": 7,
  "stage": "pre_7d",
  "event": "ssl.expiring",
  "title": "SSL certificate for Checkout API expires in 7 days",
  "url": "https://app.sutramx.com/dashboard/2d1c7a52-8f0e-4c3b-9b7a-5e6f1a2b3c4d"
}
FieldValues
kindssl or domain
days_remainingWhole days left; negative once expired
stagepre_15d, pre_7d, pre_2d, pre_24h, post_1d, post_2d, post_3d, or pre_<N>d for an early reminder at the monitor's own threshold

maintenance.started and maintenance.ended#

json
{
  "maintenance_id": "5a3f9c1e-2b4d-4e6f-8a0b-1c2d3e4f5a6b",
  "maintenance_title": "Database upgrade",
  "description": "Upgrading the primary database.",
  "starts_at": "2026-10-04T01:00:00.000Z",
  "ends_at": "2026-10-04T02:00:00.000Z",
  "impact": "medium",
  "scope": "monitor",
  "monitor_ids": ["2d1c7a52-8f0e-4c3b-9b7a-5e6f1a2b3c4d"],
  "group_ids": [],
  "event": "maintenance.started",
  "title": "Maintenance started: Database upgrade",
  "url": "https://app.sutramx.com/dashboard/maintenance"
}

impact is low, medium or high. scope is global, monitor or group. A maintenance.ended event has the title "Maintenance completed: <title>".

site_health.issues#

FieldDescription
site_id, run_idThe website health site and crawl run
site_urlThe crawled site
statusThe run's status
broken_links, mixed_contentCounts from this run
new_errorsUp to 50 new problems, each with kind and target_url
score_dropsLighthouse score drops, each with url, category, from and to
urlLink to the report

third_party.incident, third_party.resolved, third_party.maintenance#

json
{
  "provider_id": "heroku",
  "provider_name": "Heroku",
  "kind": "incident_opened",
  "severity": "major",
  "detail": "We are investigating elevated error rates.",
  "components": ["Dynos"],
  "status_page_url": "https://status.heroku.com",
  "incident_url": "https://status.heroku.com/incidents/12345",
  "event": "third_party.incident",
  "title": "Heroku reports an incident: Elevated error rates",
  "url": "https://status.heroku.com/incidents/12345"
}
FieldValues
kindincident_opened, incident_updated, incident_resolved, maintenance_started, maintenance_completed, component_degraded, component_recovered
severityoperational, maintenance, minor, major or critical

last_mile.unreachable and last_mile.recovered#

FieldDescription
monitor_id, monitor_nameThe affected monitor
notice_idThe ISP outage; the same for its unreachable and recovered events
ispsNames of the affected ISP networks
affectedOne entry per ISP with isp, name, vantage_points, failures, failing_since and error_classes
urlLink to the monitor

The title reads, for example, "Unreachable for Jio users: Checkout API" or "Reachable again for Jio users: Checkout API". See Last-mile checks.

Verify the signature#

A signed webhook, end to end
Every request is signed with your secret. Verify the signature, answer quickly with a 2xx, and de-duplicate retries on the delivery ID.

The signature is an HMAC-SHA256 of the timestamp, a dot, and the exact raw request body, keyed with your signing secret:

text
X-SutramX-Signature = "sha256=" + hex( HMAC_SHA256(secret, X-SutramX-Timestamp + "." + rawBody) )

To verify a delivery:

  1. Read the raw body bytes before any JSON parsing.
  2. Recompute the signature with your secret and compare it to X-SutramX-Signature in constant time.
  3. Reject requests whose X-SutramX-Timestamp is more than 5 minutes old.

Node.js:

ts
import crypto from 'node:crypto';

export function verifySutramX(headers: Record<string, string>, rawBody: string, secret: string): boolean {
  const timestamp = headers['x-sutramx-timestamp'];
  const signature = headers['x-sutramx-signature'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  return fresh
    && expected.length === signature.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Python:

text
import hashlib, hmac, time

def verify_sutramx(headers, raw_body: bytes, secret: str) -> bool:
    timestamp = headers.get("X-SutramX-Timestamp", "")
    signature = headers.get("X-SutramX-Signature", "")
    message = timestamp.encode() + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    fresh = timestamp.isdigit() and abs(time.time() - int(timestamp)) < 300
    return fresh and hmac.compare_digest(expected, signature)

Rotate the secret#

Open the webhook connection's menu, choose Rotate signing secret and confirm with Rotate secret. The new secret is shown once. The old secret stops working immediately, so update your receiver right away. Editing the endpoint URL keeps the current secret.

Responding, timeouts and retries#

BehaviourDetail
SuccessAny 2xx response. The response body is ignored.
Timeout10 seconds per attempt
RedirectsNot followed. A 3xx response fails the delivery: use the final URL.
RetriedTimeouts, network errors, 408, 409, 425, 429 and 5xx responses
Not retriedOther 4xx responses, which SutramX treats as permanent
AttemptsUp to 6 in total, waiting about 5, 10, 20, 40 and 80 seconds between them
Retry-AfterHonoured on 429 and 503 responses when it asks for longer, up to 15 minutes

Respond quickly with 200 or 204 and do any slow work in the background.

Each attempt is signed again with a fresh timestamp. Retries of the same delivery reuse the same X-SutramX-Delivery ID, so a handler can de-duplicate on that header. Incident payloads also include incident_id.

After 3 deliveries in a row fail, the connection is marked Failing on Alerts → Channels & API, with the last error, and the workspace owner gets an email about it (at most once a day per connection). The connection is never disabled: it keeps receiving events, and a successful delivery clears the flag.

Test your endpoint#

Open the connection's menu and choose Send test. SutramX sends a signed test event and shows the result: "Test message delivered", or the status code and a short excerpt of your endpoint's error.

To inspect payloads before writing a handler, point a webhook at a request inspector you control, send a test, then switch the URL to your real endpoint with Edit.

Zapier#

  1. In Zapier, create a Zap with the trigger Webhooks by Zapier → Catch Hook and copy the hook URL.
  2. In SutramX, click Connect Zapier on the Zapier card, paste it into Link from Zapier, and click Connect.
  3. Click Send test, then use the test event in Zapier to map fields.

Zapier receives the same JSON as monitor.down, monitor.up, incident.resolved and test above. It doesn't receive notices (expiry, maintenance, website health, third-party or last-mile events), and its requests aren't signed: they have no X-SutramX-Timestamp or X-SutramX-Signature header. The link must be a Zapier hook URL, such as https://hooks.zapier.com/hooks/catch/….

Troubleshooting#

  • "Enter a full URL starting with https://": plain http:// endpoints aren't accepted.
  • "This address isn't reachable from the internet": the host resolves to a private or local address. Expose a public HTTPS endpoint.
  • "We couldn't find that host": check the hostname for typos and that its DNS record exists.
  • Signature mismatch: make sure you sign the raw body, use the current secret, and include the timestamp and the dot.
  • "Endpoint redirected to …": use the final URL (for example add or remove a trailing slash, or switch to the canonical host).

Last updated . Something unclear or missing on this page? Tell us at support@sutramx.com.