Incidents, delivered anywhere.
SolidUptime can POST a JSON payload to any HTTPS endpoint when alert events occur on your project. Wire it into Slack, Discord, PagerDuty, Linear, or your own handler.
Delivery
| Method | POST |
| Content-Type | application/json |
| User-Agent | SolidUptime-Webhook/1.0 |
| Timeout | 10 seconds per attempt |
| Retries |
Transient failures (network errors, 429,
5xx) are retried with
exponential backoff — after 1 minute, then 5 minutes, then 15
minutes. After 3 failed attempts the job is marked permanently
failed.
4xx responses (except
429) are treated as permanent
failures and not retried.
|
Respond with a 2xx status quickly — do any
heavy processing asynchronously.
Signature verification
When a signing secret is set on your webhook channel, every request
includes an X-SolidUptime-Signature
header. The value is the HMAC-SHA256 of the raw request body, hex-encoded.
Verify it before processing the payload.
const crypto = require('crypto');
function isValidSignature(rawBody, secret, header) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
} import hmac, hashlib
def is_valid_signature(raw_body: bytes, secret: str, header: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
Always use a constant-time comparison (timingSafeEqual /
hmac.compare_digest) to prevent
timing attacks. Compute the signature over the raw request body bytes
— do not parse or reformat the JSON first.
Payload structure
Every event shares a common envelope. The data
object is specific to each event type.
| Field | Type | Description |
|---|---|---|
| event | string | Event type identifier (see below) |
| project_id | string (UUID) | ID of the project that triggered the event |
| project_name | string | Display name of the project |
| incident_id | integer | ID of the related incident. Present only on incident events. |
| data | object | Event-specific payload (see each event below) |
Event types
| Event | Trigger |
|---|---|
| incident.opened | A new incident is detected — one or more checks have crossed the failure threshold |
| incident.escalated | An additional check fails while an incident is already active |
| incident.resolved | All checks have recovered and the grace period has elapsed |
| alert.tls_expiry_warning | A monitored certificate will expire within 7 days |
| alert.test | A test delivery triggered manually from the dashboard |
incident.opened
Fired when a new incident starts — at least one check has crossed the failure threshold.
{
"event": "incident.opened",
"project_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"project_name": "My API",
"incident_id": 42,
"data": {
"severity": 2,
"started_at": "2026-03-16T10:00:00Z",
"affected_checks": [
{ "name": "Homepage", "url": "https://example.com" },
{ "name": "API health", "url": "https://api.example.com/health" }
]
}
} | data field | Type | Description |
|---|---|---|
| severity | integer | Incident severity (see Severity levels) |
| started_at | string (RFC 3339) | When the incident began. Always UTC. |
| affected_checks | array | Checks that contributed to the incident at the time of opening |
| affected_checks[].name | string | Check display name |
| affected_checks[].url | string | Check target URL |
incident.escalated
Fired when an additional check fails while an incident is already
active. The affected_checks array reflects
the full set of currently failing checks.
{
"event": "incident.escalated",
"project_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"project_name": "My API",
"incident_id": 42,
"data": {
"severity": 3,
"started_at": "2026-03-16T10:00:00Z",
"affected_checks": [
{ "name": "Homepage", "url": "https://example.com" },
{ "name": "API health", "url": "https://api.example.com/health" },
{ "name": "Checkout", "url": "https://example.com/checkout" }
]
}
}
Same data fields as incident.opened.
incident.resolved
Fired after all checks have recovered and the grace period has elapsed without a new failure.
{
"event": "incident.resolved",
"project_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"project_name": "My API",
"incident_id": 42,
"data": {
"resolved_at": "2026-03-16T10:45:00Z",
"duration_seconds": 2700.0
}
} | data field | Type | Description |
|---|---|---|
| resolved_at | string (RFC 3339) | When the incident was marked resolved. Always UTC. |
| duration_seconds | number | Total incident duration in seconds |
alert.tls_expiry_warning
Fired once per certificate when it is 7 days or fewer from expiry and the check is currently passing. Will not re-fire for the same certificate unless it is renewed and the expiry date changes.
{
"event": "alert.tls_expiry_warning",
"project_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"project_name": "My API",
"data": {
"check_name": "API health",
"domain": "api.example.com",
"days_left": 5,
"tls_expires_at": "2026-03-21T00:00:00Z"
}
} | data field | Type | Description |
|---|---|---|
| check_name | string | Display name of the check monitoring this certificate |
| domain | string | Hostname extracted from the check URL |
| days_left | integer | Days remaining until certificate expiry |
| tls_expires_at | string (RFC 3339) | Certificate expiry timestamp. Always UTC. |
alert.test
Fired when a team member sends a test delivery from the alert channel settings. Use this to verify your endpoint is receiving events correctly before relying on it for real incidents.
{
"event": "alert.test",
"project_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"project_name": "My API",
"data": {
"triggered_by": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"triggered_at": "2026-03-16T10:00:00Z"
}
} | data field | Type | Description |
|---|---|---|
| triggered_by | string (UUID) | ID of the user who triggered the test |
| triggered_at | string (RFC 3339) | When the test was triggered. Always UTC. |
Severity levels
Severity is calculated from the ratio of currently failing checks to total active checks in the project. It only ever increases during an incident — it never drops while the incident is ongoing.
| Value | Label | Threshold |
|---|---|---|
| 1 | Low | < 5% of checks failing |
| 2 | Medium | ≥ 5% of checks failing |
| 3 | High | ≥ 20% of checks failing |
| 4 | Critical | ≥ 50% of checks failing |