Sending

One endpoint, one JSON shape. Anything that can make an HTTP POST is a sender — or use the zero-dependency SDK.

Webhook

POST /v1/send
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

@saasflare-dev/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

fieldrequireddescription
categoryno — "general"Grouping key: sidebar section, unread counts, per-category settings. Free-form, ≤64 chars.
templateno — "text"Card template key. Unknown keys fall back to text — messages are never dropped.
datanoProps for the template. Missing or malformed fields degrade gracefully.
stylenoLight overrides: accent (hex), icon (emoji), urgency (low / normal / critical).
actionsno — ≤8URL buttons { label, url }. Only http(s) renders; opened in the OS browser.

Responses

statusbodymeaning
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.
Notify — a quiet notification center for builders.