Skip to content
Specific Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Alerts

Default alerts on every service, with email and webhook notifications when they fire.

Every deployed service gets a set of default alerts. They are evaluated every minute from the same telemetry you can query with specific query, they drive the badge on Alerts in the sidebar, and on paid plans they notify you when they fire. You’ll find them under Alerts in the dashboard; nothing needs to be configured.

The default alerts

Alert Severity Applies to Fires when
Elevated 5xx responses critical public services at least 5% of requests in the last 5 minutes returned a 5xx status, given at least 20 requests, for 2 consecutive minutes. Counts every 5xx the ingress saw, whether your app or the platform produced it.
Restarts and out-of-memory kills critical all services a container crashed and was restarted, or was killed for exceeding its memory limit, in the last 10 minutes.
Slow responses warning public services at least 5% of requests in the last 10 minutes took 3× longer than their route usually does and over 1 second, given at least 100 requests. See how slow responses are judged.
Health-check failures warning all services a running container failed a readiness or liveness probe in the last 10 minutes. Startup-probe failures and probes against pods that are shutting down are ignored.

“Public services” are the ones reachable through the platform ingress (the ones with a domain); only those have request data. Preview environments get the same alerts, but never send notifications and are left out of the badge.

There is deliberately no alert on error-level log lines: log severity is your application’s own semantics, and apps legitimately log errors that are not problems.

How alerts are evaluated

Each alert is one SQL query over the observability views that returns a single number, compared against a threshold. It runs every minute (every five minutes for slow responses) with the same access as specific query, so it only ever sees your own environment’s data.

Open an alert in the dashboard to see the exact query. Paste it into specific query and you get the same value the alert saw:

specific query "$(pbpaste)"

Every evaluation is recorded, so an alert’s page graphs exactly the values that were checked, and you can read the same numbers yourself from observability.alert_evaluations:

SELECT Timestamp, Value, State
FROM observability.alert_evaluations
WHERE ServiceName = 'web' AND AlertName = 'http-p99-latency'
  AND Timestamp > now() - INTERVAL 6 HOUR
ORDER BY Timestamp

An alert is either ok or firing. It resolves as soon as one evaluation is back under the threshold.

How slow responses are judged

The slow-responses alert measures time to first byte at the ingress: how long the platform waited for your service to start responding. Streamed responses and slow client downloads don’t count against you, and requests that never reached your service (a client that gave up while sending its request, for instance) are ignored.

Rather than comparing to a fixed number, it compares each route to itself. A request is “slow” if it took at least 3× that route’s own p90 over the trailing 24 hours, and at least a second. The alert fires when more than 5% of a window’s requests were slow by that definition. A report page that always takes ten seconds is therefore not an alert; a page that is suddenly slower than it usually is, is. Routes are templated (/api/users/{id}, /assets/{file}.svg), so IDs and asset file names don’t fragment the history.

The badge

Alerts in the sidebar carries a badge counting what is firing across the project, and nothing else: one firing critical alert colours the badge red, a firing warning colours it orange, and a project with nothing firing has no badge at all. The count is everything firing, not just the criticals, and it is never shortened. Click it for the alerts behind it.

Notifications

On Pro and above, a firing alert sends a notification within a minute of firing, with what fired, where, the numbers behind it (for example the failing routes and the request counts of that window), and a link to the alert. Resolving sends no notification, and an alert that fires repeatedly notifies at most once every 30 minutes.

Where alerts go is set up under Alert notifications in organization settings. A destination is a name and a list of email addresses, webhooks, or both; the organization’s members are suggested as you type, and everyone starts on the default email destination.

Which destinations an alert uses is decided on the alert’s own page, from the Notifications button in its header. The default destination reaches every alert; a destination you add reaches no alert until you switch it on there, so adding an on-call address never pages for everything at once. Switching every destination off is how you silence an alert.

Webhooks

In a destination, open Webhook and choose Add webhook. Enter a public HTTPS URL and an optional signing secret. Save it, then enable the destination from the alert’s Notifications menu.

Specific sends a JSON POST when the alert fires. The endpoint must return a 2xx status within 10 seconds. Redirects are not followed; private and reserved addresses are rejected.

The payload uses version 1. X-Specific-Event-Id matches id:

{
  "version": 1,
  "id": "alev_example",
  "type": "alert.fired",
  "createdAt": "2026-10-18T12:00:00.000Z",
  "data": {
    "eventKind": "fired",
    "alertName": "http-5xx",
    "alertTitle": "Elevated 5xx responses",
    "severity": "critical",
    "projectName": "Example",
    "environmentName": "main",
    "serviceName": "api",
    "summary": "10% of requests returned a 5xx status over the last 5 minutes",
    "facts": ["Requests: 100"],
    "startedAt": "Oct 18, 2026, 12:00 UTC",
    "alertUrl": "https://dashboard.specific.dev/projects/example/env/main/alerts/api/http-5xx"
  }
}

Failed deliveries are retried with exponential backoff, up to five attempts total. A failure at one destination can repeat delivery to others. Deduplicate by event ID and return a 2xx for events already processed.

With a signing secret, X-Specific-Signature is sha256= followed by the hex HMAC-SHA256 of timestamp + "." + rawRequestBody, using that secret. X-Specific-Timestamp is Unix seconds, refreshed on each attempt. Verify against the original body bytes, compare signatures in constant time and reject timestamps more than five minutes from your clock.

Investigating an alert

An alert’s page has what you need to decide whether it matters:

  • The graph of the alert’s value over the last six hours, with the threshold marked.
  • A per-route breakdown for the request-based alerts, so you can tell a spike that has passed from a route that is still slow or failing.
  • History: every time the alert fired or resolved, with the facts captured at that moment.
  • A prompt you can copy into your coding agent that carries the alert’s context and its query.

For the same investigation from the CLI, a few starting points:

-- What fired across this environment in the last 7 days
SELECT ServiceName, AlertName, count() AS fires
FROM observability.alert_evaluations
WHERE Outcome = 'value' AND State = 'firing' AND Timestamp > now() - INTERVAL 7 DAY
GROUP BY ServiceName, AlertName ORDER BY fires DESC
-- The slowest routes of a service in the last hour (time to first byte)
SELECT SpanAttributes['http.method'] AS method, SpanAttributes['http.route'] AS route,
  count() AS requests,
  round(quantile(0.9)(1000 * toFloat64OrZero(SpanAttributes['nginx.upstream_header_time']))) AS p90_ms
FROM observability.traces
WHERE Source = 'ingress' AND ServiceName = 'web' AND Timestamp > now() - INTERVAL 1 HOUR
GROUP BY method, route ORDER BY p90_ms DESC LIMIT 10
-- Why the health checks failed
SELECT Timestamp, ResourceAttributes['k8s.pod.name'] AS pod, Body
FROM observability.events
WHERE ServiceName = 'web' AND LogAttributes['k8s.event.reason'] = 'Unhealthy'
  AND Timestamp > now() - INTERVAL 1 HOUR
ORDER BY Timestamp DESC

See Observability for the full schema of these views.

Was this page helpful?