- Proxy notification image_url server-side (GET /api/v1/notifications/:id/image) instead of loading it directly in the browser, so LAN-only image sources (e.g. Frigate snapshots) still load for clients off that network - Tighten CSP img-src to 'self' now that images are same-origin - Update threat model / architecture docs to match the new image-fetch design - Expand README: Home Assistant quick-start, Pocket ID/OIDC walkthrough, Pangolin note, backup automation, new troubleshooting entries - Fix docker-compose.yml: document that --env-file .env must be passed explicitly (Compose's implicit .env lookup was unreliable with -f from a different directory) - Fix image-proxy tests to build a real session cookie (AUTH_MODE=oidc applies inside the test container too, so requireAuth needs one)
4.9 KiB
Migrating from ntfy
Notizarr's ingestion listener (:8081) accepts ntfy-style publish requests
at POST /:topic, so most existing ntfy integrations only need a URL and an
auth header changed — not a rewrite. This isn't a full ntfy protocol clone
(see What's different below); it covers the parts of
ntfy that are actually used for publishing a message.
Quick reference
| ntfy | Notizarr |
|---|---|
https://ntfy.sh/mytopic |
http://<lan-ip>:8081/mytopic |
| No auth, or ntfy access tokens | Authorization: Bearer nzr_... (create one in Admin → API Tokens) |
Title: header |
Title: header — same |
Priority: header |
Accepted, stored, min/1 skips push — see Priority |
Tags: header |
Not mapped yet — see What's different |
Click: header |
Click: header — same, becomes the notification's link |
Attach: header |
Attach: header — same, treated as an image URL |
Filename: header |
Filename: header — stored as metadata |
| plain-text body | plain-text body — same, becomes the message |
JSON publish ({"topic", "message", ...}) |
JSON publish — same shape, topic comes from the URL path instead |
Query params (?title=...) |
Query params — same |
Before / after examples
Plain curl, ntfy:
curl -H "Title: Backup finished" -d "Backup completed successfully" ntfy.sh/mytopic
Plain curl, Notizarr:
curl -H "Title: Backup finished" \
-H "Authorization: Bearer nzr_your_token_here" \
-d "Backup completed successfully" \
http://<lan-ip>:8081/mytopic
The only changes: the host/port, and adding the Authorization header. If
you were using ntfy's optional access tokens, this direction is the same —
you're just switching to a Notizarr-issued token with real per-service
scoping instead.
JSON publish, ntfy:
curl ntfy.sh -d '{"topic":"mytopic","title":"Backup finished","message":"Backup completed successfully","click":"https://backups.example.com"}'
JSON publish, Notizarr:
curl -H "Authorization: Bearer nzr_your_token_here" \
-d '{"topic":"mytopic","title":"Backup finished","message":"Backup completed successfully","click":"https://backups.example.com"}' \
http://<lan-ip>:8081/mytopic
Same JSON body. topic in the body is redundant with the URL path but
harmless if you leave it — Notizarr always takes the topic from the path on
this endpoint.
Getting a token
- Log into Notizarr as an admin.
- Settings → Admin → API Tokens → Create token.
- Give it a name matching the service (e.g. "Gitea", "Uptime Kuma").
- Copy the
nzr_...value immediately — it's shown once. - Optionally scope it to specific topics under "Restrict topics" so a leaked token from one integration can't publish everywhere.
What's different
Deliberately not replicated, because the goal is easy migration, not a full protocol clone (and some of these are explicit non-goals — see docs/phase1/00-README.md):
- Priority. ntfy's priority levels (min/low/default/high/urgent, or
1-5) are accepted and stored on the notification. Only
min/1changes delivery: that notification is skipped during push fan-out (still stored and shown in-app), so a single noisy sender can be silenced without a separate topic. The other levels are accepted but don't yet change anything further (no sound/vibration mapping, no urgent-bypasses-DND). Whether a topic pushes at all otherwise is controlled by that topic's push ON/OFF setting — see docs/phase1/04-api-design.md. - Tags/emoji. ntfy's
Tags:header (e.g.warning,skull) isn't parsed into emoji prefixes. If you want an emoji in the title, put it directly in theTitle:header or JSONtitlefield, same as ntfy's own emoji-in-title convention. - Attachments.
Attach:is treated as an image URL. The server fetches and proxies it (GET /api/v1/notifications/:id/image) rather than handing the raw URL to the browser — deliberately so a LAN-only image source (a camera snapshot, say) still loads for a client that's off that LAN, since the server sits on it persistently. Still no arbitrary file upload, and the fetch is narrowly scoped (see the SSRF section of docs/phase1/02-threat-model.md) — only the fixed URL already stored on a notification the caller can see, never a client-supplied one. - Auth is required by default. Unlike a bare ntfy instance, publishing
always needs a valid
Authorization: Bearertoken — there's no anonymous-publish mode.
Home Assistant, Seerr, Uptime Kuma, and other services
See docs/home-assistant.md for a worked Home Assistant
example (replacing ntfy.publish) and equivalent webhook configurations for
Seerr, Uptime Kuma, and any service with a generic webhook option.