Uptimeify Docs
Incident management

Alert Sources API

Create, configure and test Incident Management alert sources: ingest tokens, optional HMAC or Bearer authentication, payload mapping, maintenance windows and per-source statistics.

An alert source is an inbound integration. Each webhook source has its own ingest URL (https://uptimeify.io/api/im/ingest/<token>) and a payload mapping that turns the vendor's JSON into an alert. The setup guides explain how to point Zabbix, Datadog, Grafana and others at that URL. This page covers the endpoints that manage the sources themselves.

Authentication

Every endpoint needs IM access: an IM-eligible role (admin, editor, responder) or an organization-wide API token. Incident Management must be enabled for the organization. A customer-scoped token is rejected with 403 Forbidden (customerScopedTokenForbidden).

  • Read endpoints (list, detail, presets, payloads, stats, test-mapping) are open to every IM-eligible caller.
  • Write endpoints (create, update, delete, token and secret rotation, disable-auth, test-alert) additionally require the role admin or a team-admin membership in the source's team. An API token carries the role of the user who created it, so use a token created by an organization admin.

Sources are scoped to your organization. A source of another organization answers 404 Not Found (imSourceNotFound), never 403.

The source object

List, detail, update and disable-auth return this shape. Credentials never appear in it: the ingest token, the bearer token and the HMAC secret are only returned once, by the endpoint that mints them.

FieldTypeDescription
idnumberSource ID.
organizationIdnumberOwning organization.
teamIdnumberTeam whose escalation the source feeds.
namestringDisplay name, up to 120 characters.
typestringzabbix, datadog, grafana, alertmanager, sentry, custom, api, email, or uptimeify_monitoring (the built-in bridge from classic monitoring, created by the platform).
authModeobject{ url_token?, hmac?: { enabled }, bearer?: { enabled } }. See Optional request authentication.
payloadMappingobjectField mapping applied to every inbound payload.
autoResolvebooleanWhether a resolve event closes the incident automatically. Copied onto each new incident.
groupingWindowMinutesnumber or nullGrouping window. null groups by dedup key only.
maintenanceobject or nullRecurring maintenance windows, { windows: [...] }.
snoozeUntilISO 8601 or nullAd hoc mute until this instant.
secondaryExpiresAtISO 8601 or nullUntil when the previous ingest token still works after a rotation.
createdAt, updatedAtISO 8601Timestamps.
{
  "id": 7,
  "organizationId": 1,
  "teamId": 3,
  "name": "Zabbix production",
  "type": "zabbix",
  "secondaryExpiresAt": null,
  "payloadMapping": {
    "title": "trigger_name",
    "severity": "event_severity",
    "status": "trigger_status",
    "host": "host_name",
    "dedupKey": "event_id",
    "severityMap": { "Disaster": "sev1", "High": "sev2", "Average": "sev3", "Warning": "sev4", "Information": "sev4", "Not classified": "sev4" },
    "resolveValues": ["RESOLVED", "OK"]
  },
  "autoResolve": true,
  "groupingWindowMinutes": null,
  "maintenance": null,
  "snoozeUntil": null,
  "createdAt": "2026-10-01T08:00:00.000Z",
  "updatedAt": "2026-10-01T08:00:00.000Z",
  "authMode": {}
}

List sources

GET /api/im/sources

Returns all sources of your organization as an array of source objects, sorted by name.

curl "$BASE_URL/api/im/sources" \
  -H "Authorization: Bearer $TOKEN"

Get a source

GET /api/im/sources/:id

Returns one source object.

List presets

GET /api/im/sources/presets

Returns the built-in presets you can pass as type when creating a source. docsSlug is the setup guide under /api/incident-management/alert-sources/, vendorTemplate is a ready-made vendor config (only Zabbix ships one, a media type YAML), samplePayload is the test payload test-mapping and test-alert fall back to.

[
  {
    "name": "zabbix",
    "docsSlug": "zabbix",
    "vendorTemplate": "zabbix_export:\n  version: '7.4'\n  ...",
    "samplePayload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" }
  },
  { "name": "datadog", "docsSlug": "datadog", "vendorTemplate": null, "samplePayload": { "alert_id": "1234567", "alert_title": "[Triggered] High CPU usage on host web-01" } }
]

The full list is zabbix, datadog, grafana, alertmanager, sentry, custom (sample payloads shortened above).

Create a source

POST /api/im/sources

Request Body

FieldTypeRequiredDescription
teamIdnumberYesTeam in your organization.
namestringYes1 to 120 characters, trimmed.
typestringYesA preset name, api (no ingest URL, fed through Events Ingest) or email (inbound mail address).
payloadMappingobjectNoOmit it to start from the preset's mapping. An explicit value, even {}, replaces the preset. For email only the nested emailSelectors key is kept, see the Email guide.
autoResolvebooleanNoDefault true.
groupingWindowMinutesnumber or nullNoPositive integer, or null (default).
authModeobjectNoOnly url_token and enabled: false flags are accepted here. HMAC and Bearer are switched on by their own endpoints below.

payloadMapping accepts two shapes. The flat form uses string selectors title, severity, status, host, dedupKey, plus severityMap (raw value to sev1 .. sev4), resolveValues (array of strings) and custom (object of selectors). The attribute form is { "version": 2, "attributes": [...] }, each attribute { key, standard?, steps?, flags? } with steps { kind: "extract", type: "path" | "jsonpath" | "constant", expr } or { kind: "valueMap", entries: [{ from, to }], fallback? }. Both forms accept defaultSeverity (sev1 .. sev4, default sev3), used when the payload yields no valid severity.

Example (cURL)

curl -X POST "$BASE_URL/api/im/sources" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "teamId": 3, "name": "Zabbix production", "type": "zabbix" }'

Response

200 OK: the source object plus ingestToken. The token is shown only in this response. Only its SHA-256 hash is stored, it cannot be read back later. Build the webhook URL as https://uptimeify.io/api/im/ingest/<ingestToken>. For type: "api" the token is null. For type: "email" the response also carries inboundEmailAddress (<token>@alerts.uptimeify.io).

{
  "id": 7,
  "organizationId": 1,
  "teamId": 3,
  "name": "Zabbix production",
  "type": "zabbix",
  "secondaryExpiresAt": null,
  "payloadMapping": { "title": "trigger_name", "dedupKey": "event_id" },
  "autoResolve": true,
  "groupingWindowMinutes": null,
  "maintenance": null,
  "snoozeUntil": null,
  "createdAt": "2026-10-01T08:00:00.000Z",
  "updatedAt": "2026-10-01T08:00:00.000Z",
  "authMode": {},
  "ingestToken": "Q2hR4mVx9LkP0sTz7bNw1cYe5uJa3fGd"
}

Update a source

PATCH /api/im/sources/:id

Send only the fields you want to change. The write bar is checked on the current team, and on the target team when you move the source.

FieldTypeDescription
namestring1 to 120 characters.
teamIdnumberMoves the source to another team of the same organization.
payloadMappingobjectReplaces the whole mapping, it is not merged. null sets an empty mapping.
authModeobjectMerged into the stored value. hmac.enabled: true / bearer.enabled: true only work once a secret exists (see below). bearer.token_hash is rejected.
autoResolveboolean
groupingWindowMinutesnumber or nullnull clears the window.
maintenanceobject or null{ "windows": [{ "dow": [1,2,3,4,5], "from": "22:00", "to": "23:30", "timezone": "Europe/Berlin" }] }. from/to as 24 h HH:MM, dow ISO weekdays 1 (Monday) to 7, timezone an IANA zone, at most 20 windows. null clears it. Alerts inside a window are suppressed.
snoozeUntilISO 8601 or nullMutes the source until this instant. A past value is accepted and has no effect. null clears it.

The ingest token cannot be changed here, use rotate-token.

curl -X PATCH "$BASE_URL/api/im/sources/7" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "snoozeUntil": "2026-10-09T18:00:00Z", "groupingWindowMinutes": 15 }'

200 OK: the updated source object.

Delete a source

DELETE /api/im/sources/:id

Refused with 409 while the source still has open alerts or is the primary source of an open incident (anything not resolved or merged). Resolve those first. The monitoring bridge source (uptimeify_monitoring) cannot be deleted.

200 OK: { "success": true }.

The 409 (imSourceReferencesExist) body lists what blocks the delete (each list capped at 50):

{
  "statusCode": 409,
  "statusMessage": "Source is still referenced by open alerts or incidents",
  "data": {
    "code": "imSourceReferencesExist",
    "openAlertCount": 2,
    "openIncidents": [{ "id": "42", "title": "Database connection pool exhausted" }]
  }
}

Rotate the ingest token

POST /api/im/sources/:id/rotate-token

No request body. Mints a new ingest token. The previous token keeps working for 7 days (until secondaryExpiresAt), so you can update the vendor configuration without losing alerts. The new token is shown only in this response. For an email source, the new inbound address is <ingestToken>@alerts.uptimeify.io.

{
  "ingestToken": "Vn8kT1qZ4xLb7RwE0mYc2sHa9uJd6fPg",
  "secondaryExpiresAt": "2026-10-16T09:30:00.000Z"
}

Optional request authentication

The token in the URL is always required. On top of it you can require a signature or a header:

  • HMAC: the sender adds X-Uptimeify-Timestamp (Unix seconds, at most 5 minutes off) and X-Uptimeify-Signature, the hex HMAC-SHA256 of <timestamp>.<raw body> with the source's secret. A sha256= prefix is accepted.
  • Bearer: the sender adds Authorization: Bearer <token>.

A request that fails either check is answered exactly like an unknown token (404). authMode.url_token is stored but has no effect.

Set the HMAC secret

POST /api/im/sources/:id/set-hmac-secret

FieldTypeRequiredDescription
secretstringNoYour own secret, at least 16 characters. Omit it to have a 256-bit secret generated.

Enables HMAC (authMode.hmac.enabled: true). Calling it again replaces the secret, and the old one stops working immediately. The secret is stored encrypted and returned only in this response:

{ "secret": "c2VjcmV0LWV4YW1wbGUtbm90LXJlYWwtMTIzNDU2Nzg5MA", "hmacEnabled": true }

Rotate the bearer token

POST /api/im/sources/:id/rotate-bearer-token

No request body. Mints a 256-bit bearer token and enables Bearer auth. A previous bearer token stops working immediately. Only a hash is stored, the token is returned only in this response:

{ "token": "b3V0LW9mLWJhbmQtZXhhbXBsZS10b2tlbi1ub3QtcmVhbA", "bearerEnabled": true }

Disable HMAC or Bearer

POST /api/im/sources/:id/disable-auth

FieldTypeRequiredDescription
modestringYeshmac or bearer.

Sets authMode.<mode>.enabled to false and returns the source object. The secret stays stored, so PATCH with { "authMode": { "hmac": { "enabled": true } } } turns it back on without a new secret. Disabling a mode that is already off succeeds.

Test a mapping

POST /api/im/sources/:id/test-mapping

Runs the source's stored mapping against a payload without creating anything. Payload order: payload from the body, else the most recent payload the source received, else the preset's sample payload. A mapping failure is not an HTTP error: the response is 200 with ok: false.

FieldTypeRequiredDescription
payloadobjectNoThe JSON payload to map.
curl -X POST "$BASE_URL/api/im/sources/7/test-mapping" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "payload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" } }'
{
  "ok": true,
  "alert": {
    "title": "Zabbix agent is not available on Zabbix server",
    "severity": "sev1",
    "status": "open",
    "host": "Zabbix server",
    "dedupKey": "6633487",
    "custom": {}
  },
  "attributes": [
    { "key": "title", "standard": true, "value": "Zabbix agent is not available on Zabbix server", "resolved": true, "flags": {} },
    { "key": "severity", "standard": true, "value": "sev1", "resolved": true, "flags": {} },
    { "key": "status", "standard": true, "value": "PROBLEM", "resolved": true, "flags": {} },
    { "key": "host", "standard": true, "value": "Zabbix server", "resolved": true, "flags": {} },
    { "key": "correlationId", "standard": true, "value": "6633487", "resolved": true, "flags": { "grouped": true } }
  ]
}

On failure: { "ok": false, "error": "title selector resolved to nothing on this payload" }.

alert.status is resolved only when the mapped status value equals resolved (case-insensitive) after resolveValues, anything else is open. Without a dedupKey selector the dedup key is the SHA-1 of title plus host.

Fire a test alert

POST /api/im/sources/:id/test-alert

Queues a real alert through the normal ingest pipeline. It usually opens a real incident and pages whoever is on call for the source's team. Payload: payload from the body, else the preset's sample payload (no fallback to recently received payloads). The body is limited to 256 KB and 20 nesting levels, like the ingest URL.

FieldTypeRequiredDescription
payloadany JSONNoPayload to ingest. Required for api and email sources, which have no sample.

200 OK: { "enqueued": true }.

Recent payloads

GET /api/im/sources/:id/payloads

Up to the last 10 raw payloads the source received, newest first. Useful to build a mapping. Kept in a short-lived cache, so an empty list is normal for a quiet source.

{ "payloads": [ { "event_id": "6633487", "trigger_status": "PROBLEM" } ] }

Source statistics

GET /api/im/sources/:id/stats

Daily counters for the last 30 days with data, oldest first. day is a UTC calendar date. alerts counts inbound alerts, deduped those folded into an existing open alert, incidents the incidents opened.

{
  "days": [
    { "day": "2026-10-07", "alerts": 41, "deduped": 33, "incidents": 3 },
    { "day": "2026-10-08", "alerts": 12, "deduped": 9, "incidents": 1 }
  ]
}

Common errors

  • 401 Unauthorized (unauthorized) when not authenticated
  • 403 Forbidden (customerScopedTokenForbidden) for a customer-scoped token
  • 403 Forbidden (imAccessDenied) when the session has no IM-eligible role
  • 403 Forbidden (imNotEnabled) when Incident Management is not enabled for the organization
  • 403 Forbidden (imTeamWriteDenied) on a write endpoint without admin role or team-admin membership
  • 400 Bad Request (invalidRequestBody) when :id is not a positive integer, teamId/name/type is missing or invalid, a mapping/maintenance/snoozeUntil value is malformed, authMode.bearer.token_hash is sent, mode is not hmac/bearer, or a supplied HMAC secret is shorter than 16 characters
  • 404 Not Found (imSourceNotFound) when the source does not exist or belongs to another organization
  • 422 Unprocessable Entity (invalidTeamId) when teamId is not a team of the organization
  • 422 Unprocessable Entity (imAuthModeNotProvisionable) when enabling HMAC or Bearer via create/PATCH before a secret exists
  • 409 Conflict (imSourceReferencesExist) when deleting a source that still has open alerts or incidents
  • 409 Conflict (imSourceProtected) when deleting the monitoring bridge source
  • 400 Bad Request (imSourceHasNoIngestToken) when rotating the token of an api source
  • 400 Bad Request (imNoTestPayloadAvailable) when test-mapping or test-alert has no payload to use
  • 413 Payload Too Large (payloadTooLarge), 422 Unprocessable Entity (invalidJson, payloadTooDeep) on test-alert
  • 503 Service Unavailable (unavailable) when test-alert cannot queue the alert, safe to retry

On this page