Documentation

Monitoring & Alerts

Notifications

What Monocle notifies you about, where it sends it, and how to choose what you receive

Monocle notifies your team by email, in the app, and through notification channels: Slack, Discord, and your own webhooks.

What triggers a notification

EventWho is notifiedWhere
Uptime monitor down or recoveredMembers with access to the projectEmail, in-app, channels of the monitor
Endpoint alert fired or resolvedMembers with access to the project, if the alert notifies App membersEmail, in-app, channels of the alert
New or regressed exceptionsMembers with access to the projectEmail, channels of the matching rule
Issue assigned to youThe new assigneesEmail, in-app
Status of an issue changedThe assignees of the issueEmail, in-app
New comment on an issueThe assignees of the issue, except the author of the commentEmail, in-app
Weekly recapMembers with access to the projectEmail, in-app
Reply to your feedback, or status changeYou, as the author of the feedbackEmail, in-app
Usage at 80% and 100% of the planAll members of the organizationEmail
Trial ending soon, or endedOrganization adminsEmail

In-app notifications

The bell in the sidebar footer, above Settings, lists your latest notifications grouped by Today, Yesterday, and Earlier. Its badge counts the notifications you haven't seen yet, and clears when you open the bell.

Click a notification to open what it is about and mark it as read:

NotificationOpens
Issue assigned, status changed, commentThe issue
Uptime monitor down or recoveredThe page of the monitor
Endpoint alert fired or resolvedThe page of the alert
Weekly recapThe recaps of the project
Reply to your feedback, status changeThe feedback conversation

Hover a notification to mark it as read without opening it. Mark all read clears the list. Open inbox, or the i key, opens every notification, with filters on unread notifications and on categories. In the inbox, j and k move between notifications, Enter opens one, and e marks it as read.

Notification channels

A notification channel is a Slack, Discord, or webhook destination that you add once and reuse. Uptime monitors, endpoint alerts, and exception rules all pick from the same list of channels.

Organization admins manage channels in Settings → Integrations, under Notification channels. Every member can pick them in their monitors, alerts, and exception rules.

To add a channel:

  1. Click Add channel.
  2. Choose the Type: Slack, Discord, or Webhook. The type can't be changed later.
  3. Give the channel a Name, such as #prod-alerts.
  4. Paste the Webhook URL. For Slack, create an Incoming Webhook. For Discord, create a webhook in the settings of your Discord channel, under Integrations. For a webhook channel, see Webhook channels.
  5. Optionally, make it a default channel. See Default channels.
  6. Click Send test to check that the message arrives, then Add channel.

Each channel lists what uses it, such as Used by 2 alerts · 3 monitors · 1 exception route. Monocle never shows the URL of a channel again. To change it, edit the channel and paste a new URL. Every monitor, alert, and rule that uses the channel follows the change.

When you delete a channel, the monitors and alerts that used it stop notifying it. An alert left without destination keeps being evaluated. The exception rules that posted to the channel are removed, so the next matching rule applies.

Default channels

Under Use by default, a channel can be a default for:

  • Uptime monitors: every monitor that keeps the Default channels switch on.
  • Exceptions: every project without an exception rule that matches. See Exception rules.

With Only for production environments, the channel is a default only for production environments. Default channels don't apply to endpoint alerts. Each alert picks its own channels.

Exception rules

New and regressed exceptions go to the channels that match their service and environment. Each project has its own rules. Open Settings → Projects, open the project, and find the Exception alerts section. You need project manager access to change them.

Click Add rule and fill in a row:

ColumnValues
ServiceAny service, or one service
EnvironmentAny environment, or one environment
Posts toA Slack, Discord, or webhook channel. Or Mute Slack, Mute Discord, or Mute webhooks to stop posting there.

The type of the channel decides where the rule applies. Monocle resolves Slack, Discord, and webhooks separately. For each type, the most specific matching rule wins:

  1. A rule for the service and the environment.
  2. A rule for the service.
  3. A rule for the environment.
  4. A rule for any service and any environment.

A mute only stops its own type. Mute Slack for staging silences Slack for that environment, and Discord keeps posting.

The last row, Everything else, shows the organization's default channels for exceptions. A type with no matching rule posts there. If the row says Not posted, exceptions without a rule are not sent to any channel.

Exception summaries

Instead of one message per error, Monocle groups new and regressed exceptions into summaries.

  • Email: Monocle collects the issues for 60 seconds, then sends one email with up to 30 issues. After an email, the next one waits at least 5 minutes.
  • Channels: Monocle collects the issues for 60 seconds per channel, then posts one message with up to 10 open issues and the number of other open issues.

Weekly recaps

Every Monday, members receive a recap of each production environment for the past week: requests, error rate, average response time, top exceptions, and slowest endpoints.

Choose which emails you receive

In Settings → Notifications, the Email section has one switch per type of email:

SwitchEmails
Uptime alertsA monitor goes down or recovers
Endpoint alertsAn alert that notifies app members fires or resolves
Weekly recapsThe weekly summary of your projects
Exception summariesThe batched emails about new and regressed exceptions, for the current organization only

All switches are on by default. They only change emails. In-app notifications and channels are not affected. Issue, feedback, usage, and trial emails are always sent.

Webhook channels

A webhook channel sends each event as a JSON POST to your own endpoint, signed following the Standard Webhooks specification.

The URL must:

  • Use HTTPS, with no credentials and no #fragment.
  • Resolve to public IP addresses only. Monocle checks the addresses when you save the channel and again before each delivery.

Monocle doesn't follow redirects and waits up to 10 seconds for an answer. Any 2xx status is a success. Monocle retries exception summaries after a timeout, a network error, or a 408, 429, or 5xx status, with a growing delay of up to one hour. It sends other events once, without retries.

Requests

Each request has these headers:

HeaderValue
content-typeapplication/json
user-agentMonocle-Webhooks/1.0
webhook-idThe ID of the message. Retries of an exception summary keep the same ID.
webhook-timestampWhen the request was sent, in Unix seconds
webhook-signaturev1, followed by the signature, in base64

The body always has the same envelope:

JSON
{
  "type": "alert.firing",
  "timestamp": "2026-10-17T10:00:05.123Z",
  "data": {}
}

Verify the signature

When you add a webhook channel, Monocle creates its signing secret, which starts with whsec_. Organization admins can show it with Reveal secret, or replace it with Rotate secret. Rotating takes effect at once, so update your receiver right after.

The signature is an HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body>, keyed with the base64-decoded part of the secret after whsec_. The standardwebhooks package checks it for you, along with the age of the timestamp:

webhook.ts
import { Webhook } from 'standardwebhooks'

const webhook = new Webhook(process.env.MONOCLE_WEBHOOK_SECRET!)

export async function handle(request: Request) {
  const body = await request.text()

  // Throws if the signature or the timestamp is invalid
  const event = webhook.verify(body, {
    'webhook-id': request.headers.get('webhook-id')!,
    'webhook-timestamp': request.headers.get('webhook-timestamp')!,
    'webhook-signature': request.headers.get('webhook-signature')!,
  })

  return new Response(null, { status: 204 })
}

Verify the raw body, before parsing it. Parsing and serializing the JSON again can change its bytes and break the signature.

Send test on a saved channel signs with its secret. Send test in the form, before you add the channel, signs with a temporary secret, so your receiver can't verify that one.

Webhook payloads

Dates are ISO 8601 strings.

uptime.down
JSON
{
  "type": "uptime.down",
  "timestamp": "2026-10-17T10:00:05.123Z",
  "data": {
    "monitorId": "0199f2a4-6c1e-7d3a-9b2f-3c4d5e6f7a8b",
    "monitorName": "API health",
    "monitorUrl": "https://api.example.com/health",
    "errorType": "timeout",
    "environmentName": "Production",
    "applicationName": "Shop",
    "organizationSlug": "acme",
    "url": "https://monocle.sh/acme/shop/production/uptime/0199f2a4-6c1e-7d3a-9b2f-3c4d5e6f7a8b",
    "downAt": "2026-10-17T10:00:00.000Z"
  }
}

errorType is one of timeout, dns, connection, tls, http, ssrf, or unknown.

uptime.recovered
JSON
{
  "type": "uptime.recovered",
  "timestamp": "2026-10-17T10:12:05.123Z",
  "data": {
    "monitorId": "0199f2a4-6c1e-7d3a-9b2f-3c4d5e6f7a8b",
    "monitorName": "API health",
    "monitorUrl": "https://api.example.com/health",
    "environmentName": "Production",
    "applicationName": "Shop",
    "organizationSlug": "acme",
    "url": "https://monocle.sh/acme/shop/production/uptime/0199f2a4-6c1e-7d3a-9b2f-3c4d5e6f7a8b",
    "recoveredAt": "2026-10-17T10:12:00.000Z",
    "downAt": "2026-10-17T10:00:00.000Z"
  }
}

downAt can be null.

alert.firing
JSON
{
  "type": "alert.firing",
  "timestamp": "2026-10-17T10:00:05.123Z",
  "data": {
    "ruleId": "0199f2a4-7e2d-7c11-8a3b-4d5e6f7a8b9c",
    "ruleName": "p95 latency · POST /api/checkout > 800ms",
    "metric": "p95",
    "comparator": "gt",
    "threshold": 800,
    "windowMinutes": 5,
    "method": "POST",
    "route": "/api/checkout",
    "serviceName": "api",
    "applicationName": "Shop",
    "environmentName": "Production",
    "organizationSlug": "acme",
    "alertUrl": "https://monocle.sh/acme/shop/production/alerts/0199f2a4-7e2d-7c11-8a3b-4d5e6f7a8b9c",
    "endpointUrl": "https://monocle.sh/acme/shop/production/endpoints/...",
    "slowestTracesUrl": "https://monocle.sh/acme/shop/production/endpoints/...",
    "startedAt": "2026-10-17T09:58:00.000Z",
    "value": 912.4,
    "samples": 1530
  }
}
FieldDescription
metricp50, p95, p99, avg, error_rate, server_error_rate, or request_rate
comparatorgt for above, lt for below
threshold, valueIn milliseconds for latencies, in percent for error rates, in requests per minute for the request rate
valueThe value that fired the alert. Can be null.
samplesThe number of requests in the window
serviceNameCan be null for an alert created before alerts required a service
endpointUrl, slowestTracesUrlThe endpoint, and its slowest traces, around the time the alert fired
alert.resolved

The same fields as alert.firing, without value and samples, plus:

JSON
{
  "resolvedAt": "2026-10-17T10:20:00.000Z",
  "peakValue": 1040.2,
  "reason": "recovered"
}

peakValue is the worst value of the incident, or null. reason is one of:

  • recovered: the metric is back within the threshold.
  • no_data: the window had fewer requests than the minimum.
  • rule_edited: the condition of the alert changed.
  • rule_paused: the alert was paused.
exception.issues
JSON
{
  "type": "exception.issues",
  "timestamp": "2026-10-17T10:01:05.123Z",
  "data": {
    "total": 14,
    "organizationName": "Acme",
    "issues": [
      {
        "title": "PaymentTimeoutError: Payment provider timed out",
        "application": "Shop",
        "environment": "Production",
        "firstAppearance": true,
        "regression": false,
        "url": "https://monocle.sh/acme/shop/production/issues/0199f2a4-8f3e-7a22-9c4d-5e6f7a8b9c0d"
      }
    ]
  }
}
FieldDescription
totalThe number of open issues in the summary
issuesUp to 10 of them, open issues only
issues[].environmentThe name of the first environment, followed by +N when the issue happened in N more
issues[].firstAppearanceThe issue is new
issues[].regressionThe issue was resolved and happened again
test
JSON
{
  "type": "test",
  "timestamp": "2026-10-17T10:00:05.123Z",
  "data": {
    "organizationName": "Acme"
  }
}

Search documentation

Search pages and sections of the Monocle documentation