Receive Simrix Monitor alerts on your own webhook
A webhook endpoint receives a signed JSON POST whenever a monitor fails, recovers or detects a change. You choose the URL and which events to send. This page covers creating one, verifying the signature, and the exact fields that arrive.
Steps
- Open Account → Webhooks and choose Add webhook.
- Give it a name you will recognise later, and paste the URL that should receive the POST.
- Tick the events you want: Change detected, Monitor failed, Monitor recovered. All three are on by default.
- Save. The signing secret is shown once, on this screen only. Copy it somewhere safe before you navigate away, because there is no way to reveal or rotate it afterwards.
- Verify the signature on every request you receive, using the snippet below.
- Wait for a scheduled check to fire. Running a check by hand will not send anything.
How do you test it before a real failure?
There is no send-test button, and no test endpoint. That is worth knowing before you plan a rollout.
The practical approach is to point a webhook at a request-capture service such as webhook.site while you are building, confirm the shape and your signature check against a real delivery, then switch the URL to your own endpoint. Because only scheduled checks fire webhooks, you will need to wait for one, or temporarily set a monitor to the shortest interval your plan allows and point it at a URL you can break on purpose.
What can you change after creating one?
Less than you might expect. From the Webhooks page you can pause an endpoint and delete it, and that is all. There is no interface for changing the URL, the name or the selected events once an endpoint exists, and no way to reveal or rotate the signing secret.
If you need to change any of those, delete the endpoint and create a new one, which issues a fresh secret.
What does Simrix Monitor send?
A single JSON POST per event. Here is a real body for a failed check.
{
"event": "monitor_failed",
"timestamp": "2026-09-14T14:03:11.482Z",
"workspaceId": "clz9a1b2c3d4e5f6g7h8i9j0",
"monitor": {
"id": "clz8m0n1o2p3q4r5s6t7u8v9",
"name": "Checkout smoke test",
"type": "page_check",
"url": "https://shop.example.com/checkout"
},
"run": {
"id": "clz7r0u1n2i3d4e5f6g7h8i9",
"status": "failed",
"changeDetected": false,
"aiSummary": "We could not find “#place-order” on the page at step 3 of 6. It may have been renamed or moved, or the page may not have got that far.",
"failureSummary": "We could not find “#place-order” on the page at step 3 of 6. It may have been renamed or moved, or the page may not have got that far.",
"failureReasonCode": "element_not_found",
"durationMs": 31204,
"httpStatus": 200,
"screenshotUrl": "/api/artifacts/clz7r0u1n2i3d4e5f6g7h8i9/screenshot.png",
"diffScreenshotUrl": null,
"shareUrl": "https://simrixmonitor.com/r/Zq8nT2xK9pLm4vR7wYbC1dF3hJ5kN6sQ8tU0xW2yA4E"
}
}
Which events can fire?
| Event | When it fires |
|---|---|
monitor_failed |
A scheduled check on a workflow monitor failed. |
monitor_recovered |
A workflow monitor passed again after a failure. |
change_detected |
A change monitor found a meaningful difference from its baseline. |
What is in the body?
| Field | Type | Notes |
|---|---|---|
event |
string | One of the three event names. |
timestamp |
string | ISO 8601, set when the request is sent, not when the check ran. |
workspaceId |
string | Your workspace. |
monitor.id |
string | Use this to correlate with your own records. |
monitor.name |
string | The monitor's name as you set it. |
monitor.type |
string | The monitor's kind, for example page_check or change_monitor. |
monitor.url |
string | The URL the check started from. Empty string if none was set. |
run.id |
string | The check's id. |
run.status |
string | For example passed or failed. |
run.changeDetected |
boolean | True only on change_detected. |
run.aiSummary |
string or null | The plain-English summary, when one was produced. |
run.failureSummary |
string or null | Why the check failed. Null on success events. |
run.durationMs |
number or null | How long the check took. |
run.httpStatus |
number or null | The response code of the final page. |
run.screenshotUrl |
string or null | A path, not a full URL. Prefix it with your Simrix Monitor address, and note it needs a signed-in session to open. |
run.diffScreenshotUrl |
string or null | The highlighted difference image. Only on change_detected. |
run.shareUrl |
string or null | A public link to this check's evidence, when the monitor has shareable reports switched on. Opens with no sign-in, so this is the link to forward. Null when sharing is off for that monitor. |
Which headers arrive?
| Header | Value | Notes |
|---|---|---|
Content-Type |
application/json |
The body is always JSON. |
X-Simrix-Signature |
sha256=<hex digest> |
HMAC-SHA256 of the raw request body, keyed with your signing secret. |
X-Simrix-Event |
monitor_failed |
The same value as the body's event field, so you can route before parsing. |
User-Agent |
Simrix-Monitor-Webhook/1.0 |
Fixed. Useful for filtering your own logs. |
How do you verify the signature?
The signature is an HMAC-SHA256 of the raw request body, keyed with your signing secret and written as lowercase hex behind a sha256= prefix.
import crypto from "node:crypto";
// Sign the RAW body. Re-serialising parsed JSON will not match.
function isFromSimrixMonitor(rawBody, header, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(header ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
What should your receiver expect?
- Delivery is attempted once. A non-2xx response, a connection error or a timeout is logged and dropped. There is no retry, no backoff and no dead-letter queue, so treat a missed delivery as possible and reconcile from the app when it matters.
- The timeout is 10 seconds for the whole request. Acknowledge quickly and do your own work afterwards, rather than processing before you respond.
- The signature covers the raw body only. No timestamp is included, so there is no replay protection built in. If that matters, record
run.idand ignore ids you have already handled. - Change monitors only fire
change_detected. A change monitor that errors does not sendmonitor_failed, and change monitors never sendmonitor_recovered. Subscribing tomonitor_failedwill not tell you a change monitor is broken. - Failure and recovery webhooks follow the email toggles. Turning off Failure alerts or Recovery alerts on the notifications page turns off the matching webhook as well.
- Only scheduled checks send anything. Running a check by hand to try something out fires no webhook and sends no email.
14 days, no card required.
Frequently asked questions
What happens if my endpoint is down?
The delivery is lost. Simrix Monitor attempts each delivery once, with a ten-second timeout, and a failure is logged on our side and dropped. There is no retry and no queue, so design your receiver to tolerate a missed message.
Can I see whether a delivery succeeded?
Yes. Recent deliveries on the Integrations page lists each alert sent to a webhook, with whether your endpoint accepted it.
Why did my signature check fail?
Almost always because the body was parsed and re-serialised before checking. The signature covers the raw bytes of the request body, so verify against the raw body before any JSON parsing.
Can I get a webhook when a change monitor errors?
No. Change monitors only send changedetected. A change monitor that fails sends a failure email but no webhook, so webhooks alone will not tell you one has broken.
Last updated