Sending
One endpoint, one JSON shape. Anything that can make an HTTP POST is a sender — or use the zero-dependency SDK.
Webhook
curl -X POST https://<your-server>/v1/send \
-H 'Authorization: Bearer ntfy_...' \
-H 'Content-Type: application/json' \
-d '{
"category": "deploy",
"template": "deploy",
"data": {
"title": "Build finished",
"branch": "main",
"commit": "a1b2c3d",
"status": "ok",
"duration": "2m13s"
},
"actions": [{ "label": "View logs", "url": "https://ci.example.com/run/42" }]
}'SDK
import { createClient } from '@saasflare-dev/sdk';
const notify = createClient({
baseUrl: 'https://<your-server>',
token: process.env.NOTIFY_TOKEN!,
});
const { id, delivered } = await notify.send({
category: 'deploy',
template: 'deploy',
data: { title: 'Build finished', branch: 'main', status: 'ok' },
});The SDK is a typed fetch wrapper with zero dependencies; it runs in Node 18+, Workers, Deno, Bun and browsers. Failures throw NotifyError with status and the response body.
Message fields
| field | required | description |
|---|---|---|
category | no — "general" | Grouping key: sidebar section, unread counts, per-category settings. Free-form, ≤64 chars. |
template | no — "text" | Card template key. Unknown keys fall back to text — messages are never dropped. |
data | no | Props for the template. Missing or malformed fields degrade gracefully. |
style | no | Light overrides: accent (hex), icon (emoji), urgency (low / normal / critical). |
actions | no — ≤8 | URL buttons { label, url }. Only http(s) renders; opened in the OS browser. |
Responses
| status | body | meaning |
|---|---|---|
| 200 | { id, delivered: "online" } | Pushed to a connected desktop. |
| 200 | { id, delivered: "queued" } | Desktop offline; stored and replayed on reconnect. |
| 400 | { error, issues } | Invalid payload; issues lists each problem. |
| 401 | { error } | Missing or revoked token. |