Subscriber endpoints
Create, confirm and remove public subscriptions.
Every route requires subscribers.enabled; otherwise they answer
404 { "error": "This feature is disabled." }. They are unauthenticated,
which is the whole point — and the reason for the protections documented in
Public subscriptions.
POST /subscribers
Must be listed in subscribers.methods, otherwise
400 { "error": "The webhook subscription method is disabled." }.
rss is not a stored subscription.
An email address, or an HTTP(S) URL for webhooks.
Optional filter. null or omitted means every monitor.
| Response | Body | When |
|---|---|---|
202 | { "status": "pending" } | Email, confirmation required. |
201 | { "status": "subscribed" } | Confirmation not required. |
400 | { "error": "…" } | Invalid type, target or method disabled. |
503 | { "error": "…" } | Email confirmation could not be sent. |
The response is identical whether the destination was new or already subscribed. Anyone can submit someone else's address, so the subscription id — an unsubscribe capability — and the confirmation state are never revealed.
Target validation
Email targets must match a basic address shape. Webhook targets must be
HTTP(S), carry no credentials, and resolve to a publicly routable literal —
loopback, private ranges, link-local and cloud metadata, carrier-grade NAT,
multicast, IPv4-mapped IPv6 and reserved suffixes such as .local and
.internal are all rejected:
Idempotency
A subscription is identified by its destination. Repeating the request returns the same public response without sending another email. A pending confirmation is reused for 15 minutes; after that the pending row is replaced so the new token is valid.
Concurrent requests for the same destination are settled by a unique index: the loser reports the winner's subscription instead of an error.
GET /subscribers/confirm
| Response | Body |
|---|---|
200 | the subscriber record |
400 | { "error": "token is required." } |
404 | { "error": "Confirmation token is invalid or expired." } |
Tokens are single use. The response sets Cache-Control: no-store and
Referrer-Policy: no-referrer so the token is not leaked onwards.
GET /subscribers/:id/unsubscribe
Returns a small, self-contained HTML page with a confirm button. It is
noindex, no-store and depends on none of the application's assets, so it
still works when the rest of the deployment does not.
A link that unsubscribed on GET would fire whenever a mail scanner
prefetched it, which is why the destructive step is a POST.
POST /subscribers/:id/unsubscribe
The form target of that page. Returns HTML: 200 when removed, 404 when
the subscription is already gone.
DELETE /subscribers/:id
The programmatic equivalent.
| Response | Meaning |
|---|---|
204 | Removed. |
404 | Unknown id. |
Knowing a subscription id is enough to remove it. That is intentional — unsubscribing must never require an account — so treat the id as a secret and never expose it in a listing or a log.
Listing subscribers
There is no HTTP route that lists subscriptions, deliberately: it would be an address book of your visitors behind one token. Read them in-process when you genuinely need to: