Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
MDX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303---category: Referencetitle: Subscriber Referencedescription: Technical specification for managing status page subscribers and their notifications.---
A subscriber in openstatus is an entity (typically a user or an integration) that opts to receive real-time notifications and updates regarding incidents and status changes on a specific status page. Subscribers play a crucial role in maintaining transparent communication during service disruptions.
**Key functions of subscribers:**
- Receive automated alerts when monitor statuses change or incidents are updated.- Stay informed about service health without actively monitoring the status page.- Choose preferred notification channels for receiving updates.
## Ways to subscribe
Every method delivers the same events: status report updates and scheduled maintenance. Pick the one that matches where the audience wants to receive them.
| Method | Who sets it up | Where it is documented || --- | --- | --- || **Email** | The visitor, from the subscribe form on the status page | [Public self-subscription](#public-self-subscription) below || **RSS / Atom / JSON feed** | The visitor, in a feed reader or script | [Feeds](#feeds) below || **Slack channel via slash command** | Anyone in a Slack workspace with the openstatus app, from the channel itself | [Slack subscribers](#slack-subscribers) below || **Email, webhook or Slack from the dashboard** | The page owner | [Adding subscribers from the dashboard](#adding-subscribers-from-the-dashboard) below || **Slack Connect channel shared with a customer** | The page owner, scoped per customer | [Deliver status updates to customer Slack channels](/guides/slack-status-page-subscriptions) |
## Public self-subscription
Visitors subscribe through the subscribe form on the status page itself. The process involves:
1. **Entering an email address** โ the public form is email-only.2. **Opt-in confirmation** โ a verification email is sent, and the subscription only activates once the recipient confirms it.3. **Scope selection (optional)** โ where enabled, choosing which page components to be notified about instead of the whole page.
Webhook subscriptions are only created by the page owner from the dashboard. Slack channels can be subscribed either from the dashboard or from inside Slack with the slash command (see [Slack subscribers](#slack-subscribers)).
## Notification types received
Subscribers receive notifications for key events affecting the monitored services linked to the status page:
- **Incident creation** โ when a new incident is detected and published.- **Incident updates** โ when status reports are published for an ongoing incident (e.g., status changes from `investigating` to `identified`, `monitoring`, or `resolved`).- **Scheduled maintenance** โ when a maintenance window is created on the page, subscribers can be notified that it has been scheduled (see the [maintenance reference](/docs/reference/maintenance)).
Raw monitor status changes are *not* sent to status page subscribers. Those go to your configured [notification channels](/docs/reference/notification). Subscribers only receive status report updates and scheduled maintenance.
## Subscriber management
Status page administrators can manage their subscriber lists, including:
- **Viewing subscribers** โ accessing a list of all active subscribers for a status page.- **Adding/removing subscribers** โ manually adding or removing subscribers.- **Communication** โ sending ad-hoc notifications to the subscriber list (if supported by the platform).
## Adding subscribers from the dashboard
Beyond public self-subscription, administrators can add subscribers directly from the dashboard. This is useful for onboarding partners, internal teams, or automation that should receive updates without going through the public opt-in flow. Each manually added subscriber uses one of three channels:
- **Email** โ delivers updates to a contact address. By adding an email here you confirm the contact has consented to receive status updates; no confirmation email is sent.- **Webhook** โ POSTs each update to a URL. Slack and Discord URLs receive channel-native messages; any other URL receives a generic JSON payload.- **Slack** โ posts each update straight into a Slack channel through the openstatus Slack app. The bot token is resolved from the workspace integration at send time and is never stored on the subscriber. A channel can also subscribe itself with the slash command (see [Slack subscribers](#slack-subscribers)).
For every channel, you can optionally:
- **Set a display label** โ shown in place of the raw destination in the dashboard (e.g. `Supabase #incidents`).- **Scope to page components** โ leave empty to notify for the entire page, or select specific components to only notify on matching reports.

## Slack subscribers
A Slack subscriber posts every status report update and scheduled maintenance into one channel through the openstatus Slack app. There are two ways to create one.
### From the dashboard
The page owner adds a Slack subscriber from the subscribers section of the status page, picks the channel, and optionally scopes it to components. This is the path for [Slack Connect channels shared with customers](/guides/slack-status-page-subscriptions), where the owner decides which components each customer sees. It requires the [Slack agent](/docs/guides/how-to-setup-slack-agent) to be installed in your workspace.
### From Slack
Any channel in a Slack workspace that has the openstatus app installed can subscribe itself by running the slash command in that channel:
```/openstatus subscribe https://acme.openstatus.dev```
The URL is the public status page address, either the `*.openstatus.dev` subdomain or a custom domain. The subscription is created immediately without a confirmation step, the bot joins the channel if it can, and the channel receives updates for the whole page. Component scoping is not available from the command; use the dashboard for that.
Related commands, all run from the channel:
- `/openstatus subscriptions` โ list the pages this channel is subscribed to.- `/openstatus unsubscribe [status-page-url]` โ stop updates. The URL is optional when the channel follows exactly one page. Each Slack message also ends with a *Manage with `/openstatus unsubscribe`* hint.- `/openstatus help` โ show the command list.
Re-running `subscribe` on a channel that previously unsubscribed reactivates the existing subscription instead of creating a duplicate. Subscribing from Slack works against any page whose workspace is on a plan that includes subscribers; it does not require the channel's workspace to own the page.
For installation and the natural-language incident commands, see [Set up the openstatus Slack agent](/docs/guides/how-to-setup-slack-agent).
## Feeds
Every status page exposes read-only feeds that need no subscription at all. Feed readers and scripts poll them directly:
| Path | Format || --- | --- || `/feed/rss` | RSS 2.0 || `/feed/atom` | Atom 1.0 || `/feed/json` | JSON, the full page state including components |
On a password-protected page, append `?pw=your-secret-password` to read them. Formats and fields are described in the [status page reference](/docs/reference/status-page#feeds).
## Webhook subscribers
A webhook subscriber receives a POST request for every status report and scheduled maintenance update. openstatus inspects the destination URL and emits a payload tailored to it.
### Slack
A URL starting with `https://hooks.slack.com/services/` is treated as a Slack incoming webhook. The payload uses Slack [Block Kit](https://api.slack.com/block-kit) attachments โ a colored header (red/yellow/green/blue by status), status and page fields, the update message, affected components, and footer links to view, manage, and unsubscribe.
### Discord
A URL starting with `https://discord.com/api/webhooks/` is treated as a Discord webhook. The payload uses a Discord embed with a status-colored sidebar, a title linking to the event, status and page fields, the update message, affected components, and manage/unsubscribe links.
### Generic
Any other URL receives a versioned, channel-agnostic JSON payload. Use this to forward updates into your own systems. The body is shaped as:
```json{ "version": "1", "type": "status_report", "data": { "status_report": { "id": 123, "title": "Database connectivity issues", "url": "https://acme.openstatus.dev/events/report/123", "update": { "id": 456, "status": "investigating", "message": "We are investigating elevated error rates.", "occurred_at": "2026-06-26T10:00:00.000Z" }, "page": { "id": 1, "name": "Acme Status", "slug": "acme", "url": "https://acme.openstatus.dev" }, "components": [ { "id": 7, "name": "API", "impact": "major_outage" } ] } }, "subscription": { "manage_url": "https://acme.openstatus.dev/manage/<token>", "unsubscribe_url": "https://acme.openstatus.dev/unsubscribe/<token>" }}```
`page.url` is the canonical status page origin, while `status_report.url` (and `maintenance.url` below) is the deep link to that specific event. Each entry in `components` carries an `impact`. The schema permits `null` for forward compatibility, but the current sender always emits a concrete impact, falling back to `operational` for reports created before component impacts existed.
Scheduled maintenance is delivered with `type: "maintenance"` and a `data.maintenance` object carrying `starts_at` / `ends_at` in place of the `update` block. The `version` field is bumped on any breaking change, so pin to it when consuming the payload.
The **Send test** action emits the same envelope with `type: "test"` and a `data.test` object โ use the `type` discriminator to ignore it (it carries no real event):
```json{ "version": "1", "type": "test", "data": { "test": { "message": "Your openstatus webhook is configured correctly.", "timestamp": "2026-06-26T10:38:00.132Z" } }}```
You can attach **custom request headers** to every webhook request โ for example, a shared secret or an authorization token your receiver verifies.
#### Timeout and retries
Each webhook request is given **5 seconds** to complete. If your endpoint has not responded within that window, the request is aborted and treated as a failed attempt.
Failed attempts are retried with **jittered exponential backoff** (โ200 ms base delay), up to **3 retries** (4 attempts total). Only transient failures are retried:
- **Network errors and timeouts** (no HTTP response).- **`5xx` responses** โ a server-side error on your end that may clear.- **`429 Too Many Requests`** โ your endpoint asked us to back off.
Any other `4xx` response (e.g. `400`, `401`, `404`) is treated as a permanent client error and is **not** retried, since replaying the same request would fail again. Make your receiver **idempotent** โ a retried delivery is identical to the original, so deduplicate on the update `id` if you act on each request.
#### Example: post updates to X / Bluesky
[`statuspage-socials-notifier`](https://github.com/openstatusHQ/statuspage-socials-notifier) is a reference receiver that consumes the generic payload and cross-posts each update to your X (Twitter) and Bluesky accounts. Point a generic webhook subscriber at its endpoint to mirror your status page on social media โ it doubles as a worked example of validating and handling the payload.
#### Validating the payload
The payload shape is defined in [`packages/subscriptions/src/payload.ts`](https://github.com/openstatusHQ/openstatus/blob/main/packages/subscriptions/src/payload.ts). Copy this zod schema to validate incoming requests in your own receiver:
```tsimport { z } from "zod";
export const WEBHOOK_PAYLOAD_VERSION = "1" as const;
const impactSchema = z.enum([ "operational", "degraded_performance", "partial_outage", "major_outage",]);
const statusSchema = z.enum([ "investigating", "identified", "monitoring", "resolved",]);
const componentSchema = z.object({ id: z.number().int(), name: z.string(), impact: impactSchema.nullish(),});
const pageSchema = z.object({ id: z.number().int(), name: z.string(), slug: z.string(), url: z.url(),});
const subscriptionSchema = z.object({ manage_url: z.string().nullish(), unsubscribe_url: z.string().nullish(),});
const statusReportWebhookSchema = z.object({ version: z.literal(WEBHOOK_PAYLOAD_VERSION), type: z.literal("status_report"), data: z.object({ status_report: z.object({ id: z.number().int(), title: z.string(), url: z.url(), update: z.object({ id: z.number().int(), status: statusSchema, message: z.string(), occurred_at: z.string(), }), page: pageSchema, components: z.array(componentSchema), }), }), subscription: subscriptionSchema,});
const maintenanceWebhookSchema = z.object({ version: z.literal(WEBHOOK_PAYLOAD_VERSION), type: z.literal("maintenance"), data: z.object({ maintenance: z.object({ id: z.number().int(), title: z.string(), url: z.url(), message: z.string(), starts_at: z.string().optional(), ends_at: z.string().optional(), page: pageSchema, components: z.array(componentSchema), }), }), subscription: subscriptionSchema,});
const testWebhookSchema = z.object({ version: z.literal(WEBHOOK_PAYLOAD_VERSION), type: z.literal("test"), data: z.object({ test: z.object({ message: z.string(), timestamp: z.string(), }), }),});
export const webhookPayloadSchema = z.discriminatedUnion("type", [ statusReportWebhookSchema, maintenanceWebhookSchema, testWebhookSchema,]);
export type WebhookPayload = z.infer<typeof webhookPayloadSchema>;```
## Related resources
- **[Status page reference](/docs/reference/status-page)** โ detailed information on managing and configuring status pages, including the feed formats.- **[Set up the openstatus Slack agent](/docs/guides/how-to-setup-slack-agent)** โ install the Slack app that powers Slack subscribers and the slash commands.- **[Deliver status updates to customer Slack channels](/guides/slack-status-page-subscriptions)** โ component-scoped subscriptions for Slack Connect channels.- **[Notification channels reference](/docs/reference/notification)** โ technical specifications for the various notification delivery methods.- **[Incident reference](/docs/reference/incident)** โ information about incident creation and management.