Skip to content

ntfy Compatibility

Crit Alarm is designed as a drop-in target for ntfy publishers. Any tool, library, or script that knows how to send notifications to an ntfy server can publish to Crit Alarm without changing code.

The wire protocol matches ntfy. These four things differ on purpose:

  1. No Public Topics: Upstream ntfy allows publishing to arbitrary public topic names without authentication. Crit Alarm requires an explicit topic token (tk_...) for every publish operation. Unauthenticated requests return 401 Unauthorized.
  2. Escalation into Incidents: When priority 5 is published to a topic with critical: true, Crit Alarm opens an incident, starts a recurring repeat loop, and tracks two-stage acknowledgment.
  3. No Scheduled Delivery: Crit Alarm explicitly rejects delayed messages (X-Delay).
  4. Polling vs. Streaming: In v1, message retrieval requires poll=1 (GET /{topic}/json?poll=1). Long-lived streaming endpoints (/sse, /ws, and /raw) return 501 Not Implemented.

Header names are case-insensitive. Each header supports standard ntfy aliases:

Canonical Header Aliases Type Default Behavior
X-Title Title, ti, t string topic name Title displayed on the mobile lock screen.
X-Priority Priority, prio, p 1 to 5 or name 3 Priority level. Level 5 opens an incident on critical topics.
X-Tags Tags, tag, ta comma list none Stored and displayed in the app. Emoji shortcodes are rendered.
X-Click Click URL none Web URL opened when the alert notification is tapped.
X-Markdown Markdown, md true, 1, yes off Renders the notification body as Markdown in the app.

Parameters can also be specified in the query string:

  • ?t= or ?title= Title
  • ?p= or ?priority= Priority (1 to 5 or urgent, high, default, low, min)
  • ?ta= or ?tags= Comma-separated tags
  • ?m= or ?message= Message text

Many monitoring tools and libraries include extra ntfy headers by default. To maintain full compatibility without throwing unexpected HTTP errors, Crit Alarm accepts and ignores the following headers in v1:

  • X-Actions (custom notification action buttons)
  • X-Attach (binary file attachments)
  • X-Filename (attachment filename)
  • X-Icon (custom notification avatar/icon)
  • X-Email (forwarding to email)
  • X-Call (phone call integration)
  • X-Template (ntfy templating)
  • X-Cache (caching directives)
  • X-Firebase (upstream FCM overrides)
  • X-UnifiedPush (UnifiedPush discovery headers)

Requests carrying these headers return 200 OK, so upstream tools keep working.


X-Delay and its aliases (At, In, or JSON field delay) are explicitly rejected with HTTP 400 Bad Request:

{
"code": 40000,
"http": 400,
"error": "scheduled delivery not supported"
}

Returning 200 OK for a delayed alert the server cannot schedule is dangerous:

  • Delivering the alert immediately causes false alarms at the wrong time.
  • Dropping the delay and never alerting causes catastrophic missed outages.

Rejecting the request with an explanatory error informs the calling service immediately that scheduled delivery is unsupported.


The JSON response returned by POST /{topic} matches ntfy’s message object:

{
"id": "m_7f3k2p9q",
"time": 1757462400,
"expires": 1757505600,
"event": "message",
"topic": "prod",
"title": "Uptime Kuma",
"message": "db01 is down",
"priority": 5,
"tags": ["warning"],
"incident_id": "inc_9a8b7c"
}

Because incident_id is an additive field, existing ntfy SDKs in Go, Python, Node.js, and other languages parse this response successfully without schema errors.