# Changelog All notable changes to FlareWatch will be documented in this file. ## 3.6.0 - 2026-11-09 ### Added - Set `timezone` in the page config to show timestamps in one IANA time zone for every visitor, with the offset at each instant. UTC stays the default, and uptime days stay on UTC. ([#108](https://github.com/saminnet/flarewatch/issues/108)) - `confirmVia` accepts a list of up to five places, and `confirmQuorum` sets how many must answer before their vote counts. FlareWatch records the primary error with `(confirmed elsewhere)`, or `(no confirmation available)` when too few places answer. See [Monitors](docs/monitors.md#confirm-from-a-second-place). - Operators can acknowledge an open incident from its entry in History or on the monitor's page. Acknowledgement stops reminders, and recovery alerts name the responder. Monitor cards show who acknowledged it and when. Visitors see only when. - Manual incidents have a status, affected monitors and up to 20 progress updates. They appear on the dashboard, History and Atom feed without changing monitor status or uptime. - Visitors can subscribe to incident and recovery emails for published monitors from a Get updates button in the page header. Manual incidents send no email. Operators see the subscriber count in their account menu. Mail goes out through Resend or Cloudflare Email Service, with double opt-in, one-click unsubscribe and daily send limits. When the provider quota runs out, mail waits for the next UTC day, and an incident that recovers before its mail goes out sends none. Turnstile is optional. See [Email subscribers](docs/status-page.md#email-subscribers) for setup. - `hubLocation` in `packages/config/src/worker.ts` tells Cloudflare where to create your hub, for example `'weur'` for western Europe. It only counts when the hub is first created, so an existing hub stays where it is. See [Deploy](docs/deploy.md#hub-location). - AI agents can read published status, monitor uptime and recent incidents through the status page's `/mcp` endpoint. It needs no sign-in and returns 404 on private-only pages. ### Fixed - A reopened incident gets a new down alert even when the prior alert is still in flight. A late delivery report no longer marks the reopened incident as notified. - A fork that replaces the demo monitors passes CI and deploys again. ([#111](https://github.com/saminnet/flarewatch/issues/111)) - A username and password typed while the sign-in page is still loading now sign you in. Before, the Sign in button stayed disabled until you typed again. - The main status page, left open, picks up each new check run, about once a minute. Before, it waited until its data was 5 minutes old. The "Data is stale" warning still waits 5 minutes. A page in a background tab waits until you come back to it. - The page no longer shows "(0s ago)" for a moment while it loads. - When a monitor fails again while its outage waits to close, an error-change alert that was being sent at that moment is handled as it would be otherwise. If no webhook accepted it, the outage gets its slot back. If a busy check run put it off, it goes out on a later run. Before, it was lost and used up one of the outage's five. A reminder sent at that moment now counts, so the next reminder has the right number. ([#103](https://github.com/saminnet/flarewatch/pull/103)) ### Changed - Summary counts use status colours with plain text, so an operational page no longer shows a filled accent badge. ([#109](https://github.com/saminnet/flarewatch/issues/109)) - A refused expiry warning is tried again after 60 check runs, about an hour. Before, it wasn't tried again. - Check now starts the check while it looks up the Cloudflare location, so it no longer waits for that lookup first. Scheduled runs still finish the lookup first: a Worker can have only six connections waiting at once, and a seventh would add its wait to one monitor's latency. - Pages load faster when your hub is far from the visitor. The status page now keeps what visitors can see for 20 seconds in each Cloudflare location, and a monitor's response times for 30 seconds, so most views skip the trip to the hub. Signed-in views still read monitor status from the hub every time. An edit you make can take up to 40 seconds to reach visitors. - Dependencies updated, including pnpm 12, React 19.3, TanStack Start 1.169 (its first stable release) and Vite+ 1.2. After you pull, run `vp install`, and Vite+ fetches pnpm 12 for you. ([#104](https://github.com/saminnet/flarewatch/pull/104)) ## 3.5.0 - 2026-10-11 ### Added - The status page sends link-preview tags (Open Graph and a Twitter card) on every page. A shared link now shows the page title and a short description instead of a bare URL. ([#99](https://github.com/saminnet/flarewatch/pull/99)) ### Fixed - Webhook alerts work again. 3.4.0 told fetch to refuse redirects with a setting that Cloudflare Workers rejects, so every webhook alert failed. Every DNS, flarewatch-proxy and Globalping check failed for the same reason. A redirect now fails the request, as 3.4.0 meant it to, without sending your webhook token or proxy token on to another host. ([#101](https://github.com/saminnet/flarewatch/issues/101), [#102](https://github.com/saminnet/flarewatch/pull/102)) - A Globalping check fails when a poll answers with a redirect or an error. Before, a redirect that carried a finished result could pass the check. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - Mattermost and Rocket.Chat alerts show their content. They used Slack's block layout, which neither app reads, so an alert came through as an empty colored bar. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - Sign-in with an OpenID Connect provider or GitHub fails when the token endpoint or GitHub's API answers with a redirect. Before, it followed the redirect and sent the client secret or the access token along. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - The first check run of a new hub ends inside the 30 seconds that Cloudflare gives it. It sends an alert only when the alert can go out in time, and leaves the rest to the next run. A webhook with a `timeout` of 27 seconds or more always waits for the next run. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - The config check fails a webhook with no template whose `payloadType` is `'param'` or `'x-www-form-urlencoded'` and that has no `payload` object. Before, the check passed it and every alert to it failed. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - On a phone, a monitor's 90-day bar no longer draws every day before it has measured how many fit. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - An error-change alert that a busy check run had to put off now goes out on a later run. Before, it was lost unless the error changed again. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - The hub keeps every row under the 2 MB that Cloudflare documents. History parts are measured in bytes, so long errors in non-Latin scripts no longer make a part too big. The upgrade splits any larger part that 3.2.0 to 3.4.0 wrote, and cuts an incident from before 3.2.0 down to its first and its latest 99 error changes. CI fails a config whose monitor ids are so long that an hour of latency samples could not fit in one row. ([#102](https://github.com/saminnet/flarewatch/pull/102)) ### Changed - Wrangler 4.149.0, with workerd 1.20261006.1. ([#102](https://github.com/saminnet/flarewatch/pull/102)) - The docs say that Cloudflare counts the ping and sign-in limits in each of its locations, and only roughly. Signing out ends the session at once where you are, and can take a minute or more to reach Cloudflare's other locations. ([#102](https://github.com/saminnet/flarewatch/pull/102)) ## 3.4.0 - 2026-10-09 A certificate close to expiry now shows its monitor as degraded and sends one warning, instead of taking it down. If a monitor is down today only because of its certificate, its next check closes that outage, and the outage stays in History. You can roll back to 3.3.0 on the same storage. Take the new fields out of your config first, because 3.3.0 rejects fields it doesn't know. A certificate inside its warning window then takes its monitor down again. Read the entries marked **Needs attention** before you upgrade. Each one changes something you may rely on, or asks you to change a setting. ### Added - `checkEveryMinutes` checks a monitor every 1 to 1440 minutes instead of every minute. Monitors with the same interval are spread over different minutes. A monitor that isn't due keeps its status. `reminderEveryChecks` still counts check runs, one a minute. ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad)) - `downAfterChecks` opens an outage only after 1 to 10 failed checks in a row. The default is 1, as before. The outage starts at the first failed check of the streak. ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad)) - A certificate inside `sslCheckDaysBeforeExpiry` shows its monitor as degraded and sends one warning for each expiry date. It opens no outage and doesn't change uptime. A maintenance window pauses the warning. An expired or invalid certificate still fails the check. flarewatch-proxy counts whole days, so it fails a valid certificate in its last 24 hours. ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad), [d1ce297](https://github.com/saminnet/flarewatch/pull/95/commits/d1ce2972182061675e07415d0e17baf5a285b9a9)) - `DNS` monitors look up an `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS` or `CAA` record over DNS-over-HTTPS. `dnsExpected` lists values the answer must hold. They ask `cloudflare-dns.com` unless you set `dnsResolver`. See [Monitors](docs/monitors.md). ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad)) - `DOMAIN` monitors read a domain's expiry date over RDAP, once a day. Inside `domainExpiryDays`, 30 by default, the monitor shows as degraded and sends one warning for each expiry date. An expired domain, a failed lookup or a TLD with no HTTPS RDAP service fails the check. ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad)) - `notification.summaryAfter` sends one message instead of many. When a check run has that many alerts or more for one webhook, from 2 to 50, the webhook gets a single message that lists them. A long list is cut at 1,900 characters and says how many it left out. ([a74ac9a](https://github.com/saminnet/flarewatch/pull/95/commits/a74ac9a9a3aee6ed6c0e50a3aa3a845b4d5aa1ad)) - `checkProxy: 'vpc'` and `confirmVia: 'vpc'` check a target in your private network through a Workers VPC binding and a Cloudflare Tunnel. Workers VPC is in beta and free on every plan. You add the binding to `wrangler.toml` yourself. Without it, a `vpc` monitor fails with an error that says so. See [Monitors](docs/monitors.md#other-regions-and-private-networks). ([22da72e](https://github.com/saminnet/flarewatch/pull/95/commits/22da72e94632e2736d222dee451fd6c57346197f)) - `/api/badge.svg` serves the badge as an image, with the same parameters as the JSON badge. See [API, badges and embeds](docs/status-page.md#api-badges-and-embeds). ([5cff0f5](https://github.com/saminnet/flarewatch/pull/95/commits/5cff0f596eeee861b73f2ecc5ccb03cd24a9b3d9)) - Announcements: the operator posts a notice from History or `POST /api/admin/announcements`, and it shows on the dashboard under the status banner until it ends. The hub keeps at most 50. See [Announcements](docs/status-page.md#announcements). ([5cff0f5](https://github.com/saminnet/flarewatch/pull/95/commits/5cff0f596eeee861b73f2ecc5ccb03cd24a9b3d9), [d1b239c](https://github.com/saminnet/flarewatch/pull/95/commits/d1b239ce8591af61cec3740209d41bb474655aa5), [032ce34](https://github.com/saminnet/flarewatch/pull/95/commits/032ce34ef22fe6445207684fe96407482ab4cb61)) - `/feed.atom` is an Atom feed of the newest 50 public incidents, maintenance windows and announcements. See [Atom feed](docs/status-page.md#atom-feed). ([5cff0f5](https://github.com/saminnet/flarewatch/pull/95/commits/5cff0f596eeee861b73f2ecc5ccb03cd24a9b3d9)) - `responseHeaderEquals` and `responseJsonPath` work through flarewatch-proxy 2.0.0 or later, as `checkProxy` or `confirmVia`. An older proxy fails these checks with a message that asks you to update it, because it would skip them and pass. See the table in [Monitors](docs/monitors.md#other-regions-and-private-networks). ([#94](https://github.com/saminnet/flarewatch/pull/94)) - `packages/config/src/demo` holds the config of demo.flarewatch.app. The deploy uses it only in `saminnet/flarewatch`, so your fork keeps deploying `worker.ts` and `public.ts`. ([5cff0f5](https://github.com/saminnet/flarewatch/pull/95/commits/5cff0f596eeee861b73f2ecc5ccb03cd24a9b3d9)) ### Changed - **Needs attention.** `callbacks.onStatusChange` runs only when a monitor goes down or comes back up. Before, it also ran when the error text of a down monitor changed. `onIncident` still runs on every down run. ([8c8f6eb](https://github.com/saminnet/flarewatch/pull/95/commits/8c8f6eb2f4f7f2694b8d7563f18f62b1fe5ee5c2)) - The deploy workflow deploys only from `main`. Before, running it by hand on another branch deployed that branch. ([dfed75e](https://github.com/saminnet/flarewatch/pull/95/commits/dfed75e9c04d3aa26d5909d71297c3f2a0242f3f)) - **Needs attention.** The deploy warns when `HEARTBEAT_SECRET` or `FLAREWATCH_AUTH_SECRET` is shorter than 32 characters, because anyone with one ping URL, or who starts a sign-in, can guess a short secret offline. Replace it with the output of `openssl rand -base64 32`. A new `HEARTBEAT_SECRET` changes every ping URL. A new `FLAREWATCH_AUTH_SECRET` only fails the sign-ins in progress. ([b889e5d](https://github.com/saminnet/flarewatch/pull/95/commits/b889e5d3246712c77f71d9c483128230df60a5ff), [b904e7f](https://github.com/saminnet/flarewatch/pull/95/commits/b904e7f870d8f87fc56324234ddac7a4372ff6e2)) - **Needs attention.** Webhook, `checkProxy` and Globalping requests no longer follow redirects. A redirect fails the request, because each hop spends one of the requests that a check run keeps for checks and alerts. If your webhook or proxy URL redirects, set it to the URL it redirects to. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd)) - In light mode, uptime percentages on monitor cards and embeds use darker text, so they pass WCAG AA contrast: about 5:1 on white, up from 2.1:1 to 3.8:1. Status icons, the Check now result, the Ongoing label on incidents and the Info severity badge use the same darker colors. Dark mode is unchanged. The weekday letters in the uptime calendar and the incident times in its day view are now 10px. ([#96](https://github.com/saminnet/flarewatch/pull/96)) ### Fixed - When a check run can't afford one request per alert, each webhook now gets the alerts that don't fit as one summary in the same run. Before, recovery alerts past the run's 50 requests were never sent, and down alerts waited for later runs. ([44511ad](https://github.com/saminnet/flarewatch/pull/95/commits/44511adea39d9917de5a23d75590ccad8c5306f7)) - A recovery that a check run has no requests left for now waits for a later run. So does the recovery of an outage that ends while its down alert is still going out, which used to go out in the same run. Before, both could be lost. A waiting recovery is dropped if its monitor goes down again first. ([10a3302](https://github.com/saminnet/flarewatch/pull/95/commits/10a3302513512eaba42dfd9c0299d2356ccf81e4)) - **Needs attention.** A check run no longer starts more checks than its 50 requests allow. The checks that don't fit skip that minute and keep their status. The starting point moves each minute, so the same checks don't miss every time. CI now fails a config whose checks due every minute need more than a run has on their own: 47 requests, less one per webhook in the config. A DOMAIN check counts as two. ([b203aee](https://github.com/saminnet/flarewatch/pull/95/commits/b203aee3e054fb4a60331f59f0ca4b62d7b440ff)) - Returning to the dashboard from a monitor page or History no longer makes an extra server call to load collapsed groups. ([28eef7a](https://github.com/saminnet/flarewatch/pull/95/commits/28eef7a89ceacfa00ecc05336ba4ef26ba0ef15c)) - Hovering a monitor on the dashboard no longer loads its latency. The monitor page loads it once you open it. ([6750af6](https://github.com/saminnet/flarewatch/pull/95/commits/6750af6c129792cd295a497984943864ff8cbd0f)) - A Globalping check with `sslCheckEnabled` now fails with `Invalid certificate expiry date` when Globalping reports an expiry that isn't a date. Before, the check passed and stored the broken date. ([8f7e464](https://github.com/saminnet/flarewatch/pull/95/commits/8f7e464841e112c7ee198fc55928ebab39ec590b)) - A monitor id of `__proto__` now fails the config check with `id cannot be __proto__`. ([8f7e464](https://github.com/saminnet/flarewatch/pull/95/commits/8f7e464841e112c7ee198fc55928ebab39ec590b)) - When someone else signs in on the same browser, every open tab of the status page reloads as that account. Before, an open tab could still show the previous account's private monitors, for example after leaving visitor view. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd), [c8c0b13](https://github.com/saminnet/flarewatch/pull/95/commits/c8c0b137cfc595434f7db5a8065ea17afcbce9bc)) - The first check run that a page view starts on a fresh deploy now runs once, even when several status-page instances serve the first visitors. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd)) - A repeating maintenance window with a start date near the end of the JavaScript date range no longer stops the check runs of every monitor. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd)) - When the Worker can't find its data center, the checks of one run share that failed lookup, and the next run tries again. Before, each check tried again and spent requests that the run keeps for checks and alerts. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd)) - A proxy that sends back its `FLAREWATCH_PROXY_TOKEN` in its location, or as a reply that isn't JSON, no longer shows the token on the status page or in latency history. ([006a3bb](https://github.com/saminnet/flarewatch/pull/95/commits/006a3bb9f002e2f306a06b5cb040544e6c7d5edd), [c8c0b13](https://github.com/saminnet/flarewatch/pull/95/commits/c8c0b137cfc595434f7db5a8065ea17afcbce9bc)) - A sign-in link with a `next` path such as `/.//example.com/` no longer sends you to another site after you sign in. ([16e7904](https://github.com/saminnet/flarewatch/pull/95/commits/16e7904b67763a2c59f5dedaeed8799814b905f2)) - A config with `expectedCodes: []`, an empty `responseKeyword` or `responseForbiddenKeyword`, or `responseHeaderEquals: {}` now fails the config check, so the unit tests fail and the deploy stops. Before, an empty `expectedCodes` failed every check with "Expected status , got 200", and the other three checked nothing. ([#94](https://github.com/saminnet/flarewatch/pull/94)) - A target URL with a username and password, such as `https://user:secret@example.com`, now fails the config check. A Worker dropped them and sent the request without them, so the check never signed in. Send them in `headers` instead. ([#94](https://github.com/saminnet/flarewatch/pull/94)) - A header value that isn't a string or a number now gets the error `headers must map names to strings or numbers`. Before, the error was a bare `Invalid input`. ([#94](https://github.com/saminnet/flarewatch/pull/94)) - A direct check now follows redirects itself, with the same rules as flarewatch-proxy. When a target redirects to another site, the `Cookie`, `Authorization` and `Proxy-Authorization` headers from the monitor no longer travel with it. Before, the runtime kept `Cookie` and `Proxy-Authorization`. A redirect to a URL with a username and password, or past 20 hops, fails the check. Each redirect counts against the check run's 50 requests, so a monitor that runs out fails instead of the whole run. ([#94](https://github.com/saminnet/flarewatch/pull/94), [b889e5d](https://github.com/saminnet/flarewatch/pull/95/commits/b889e5d3246712c77f71d9c483128230df60a5ff)) - A latency chart with a single sample draws it in the middle instead of at the left edge. ([f986eea](https://github.com/saminnet/flarewatch/pull/95/commits/f986eea5c1ff188ddff37ae857d07d0d7efa59a9)) - `/api/badge` and `/api/badge.svg` answer 200 with an `unknown` badge for a monitor that has no result yet. Before, they answered 404, and some browsers then showed a broken image. A missing or private monitor still gets 404. ([#96](https://github.com/saminnet/flarewatch/pull/96)) - An uptime below 100% never shows as 100%. Before, an embed showed 99.96% as 100.0%. Embeds also color a heartbeat monitor's uptime by its state, as the dashboard does, so a down heartbeat no longer shows green. ([#96](https://github.com/saminnet/flarewatch/pull/96)) - Deleting a repeating maintenance window now says that every occurrence goes with it, and shows how it repeats. History lists each occurrence of the window, and the delete button on any of them removes the whole series. ([#98](https://github.com/saminnet/flarewatch/pull/98)) ## 3.3.0 - 2026-10-03 Deploys no longer check for 1.x data in KV. If you are still on 1.x, follow the 3.0.0 entry below first. You can roll back to 3.2.0 on the same storage. A repeating maintenance window then shows as a one-off on its first date, and reminders count from the rollback. Nothing is lost, and updating again restores both. Your config's monitors are now checked field by field. A misspelt or unknown field, or a field of the wrong type, fails the unit tests and stops the deploy. Before, FlareWatch ignored it. ### Added - Set `maxLatencyMs` on a website or API monitor to show it as degraded while its last check is slower than that. It sends no alert, opens no incident and doesn't change uptime. A maintenance window that covers the monitor keeps it up. - `/api/data` gives each monitor a `status`: `up`, `degraded`, `down`, `pending` or `running`. - **Check now** on a check monitor's page, for the operator. It runs that monitor's check once and shows the result there, without saving it. Scripts can do the same through `POST /api/admin/check`. - `confirmVia` checks a failing monitor once more from a second place, such as your own flarewatch-proxy or a Globalping probe, and records that result. A blip on one network path no longer opens an incident. See [Confirm from a second place](docs/monitors.md#confirm-from-a-second-place). - `responseHeaderEquals` checks response headers, and `responseJsonPath` with `responseJsonValue` checks one value in a JSON response. See [Monitors](docs/monitors.md#websites-and-apis). - Maintenance windows can repeat every day, week or month, on chosen weekdays or a day of the month, until a date or for good. Each run keeps its clock time in the time zone you pick, across daylight saving changes. The dashboard shows the current or next run, and History lists every run. Down alerts, error changes and reminders pause during each run. An outage that alerted before the run still sends its recovery. See [Repeating windows](docs/status-page.md#repeating-windows). - Send a monitor's alerts to some channels only: give a webhook `monitors`, a list of monitor IDs. Leave it out and the webhook gets every monitor's alerts, as before. See [Routing](docs/alerts.md#routing). - Reminders while a monitor stays down: set `reminderEveryChecks` on a monitor, at least 30. See [Reminders](docs/monitors.md#reminders). - The status page sends a Content-Security-Policy. Scripts run only from the page's own origin or with a nonce that changes on every request. See [API, badges and embeds](docs/status-page.md#api-badges-and-embeds). ### Changed - A late job's badge says `DEGRADED` in yellow instead of `UP`. The badge's `degraded` and `colorDegraded` parameters change that. - Embeds show late, pending and running jobs the way the dashboard does. - A job that starts again while its outage is still open shows as down until it succeeds, on its row, in the banner and in the API. - A monitor that asks its check location for something it can't do fails with an error that names the setting, and the unit tests fail on it before deploy. Before, `sslCheckEnabled` on a check from the Worker, or on a proxy check with `checkProxyFallback`, passed without looking at the certificate, and `pingProtocol: 'icmp'` outside Globalping quietly ran a TCP check. See the table in [Monitors](docs/monitors.md#other-regions-and-private-networks). - Every check ends within 55 seconds of the check run's start. A longer `timeout` is cut to what is left. - A fallback or confirmation check, or an extra Globalping poll, runs only while the run has subrequests to spare under the free plan's 50, after the hub's calls and one request per alert webhook. No check sends a request after the run's 55 seconds. - A `TCP_PING` monitor with `expectedCodes`, `responseKeyword`, `responseForbiddenKeyword`, `responseJsonPath`, `responseHeaderEquals` or `sslCheckEnabled` now fails. Before, it passed on the connection alone. - `checkProxy: 'worker://...'` is gone. Remove it from your config. - The admin API rejects a maintenance window with a title, color or monitor list of the wrong type. Before, it dropped the field, and a `monitors` value that wasn't a list covered every monitor. A rejected window's error now says what's wrong. `null` leaves a field out when you create a window, as it already cleared one when you update it. - The config check rejects a webhook with a field FlareWatch doesn't know, so a misspelt `monitors` can't route every alert to that channel. In `FLAREWATCH_WEBHOOKS` the Worker logs the field, ignores it and still alerts the webhook. - An error change that no webhook accepts no longer counts toward the 5 an outage may send. - A Globalping measurement over 1 MiB fails the check, the same limit a direct check puts on a response body. Before, the limit was 4 MiB. - A maintenance window's description can be at most 2000 characters, its title 200, its color 64, and it can list at most 100 monitors with IDs of at most 100 characters. The hub keeps at most 100 windows and refuses a new one past that. You can still edit the ones it has, and a window saved before these limits still shows and pauses alerts. - The config check rejects a `statusPage` field FlareWatch doesn't know, so a misspelt one fails the deploy instead of being ignored. `theme`, `customCss`, `themeVars` and `poweredByUrl` are gone from `statusPage`. The first two did nothing. For `themeVars`, set the colours in `apps/status-page/src/styles.css` instead; the `:root` and `.dark` blocks at the top hold every variable. The footer link always points to the FlareWatch repository. - `logo` and `favicon` take a `data:image/` URL only for PNG, JPEG, GIF, WebP or ICO. An SVG data URL now fails the config check; use a path or an `https:` URL to the SVG file instead. - `logo` and `favicon` take a path, an `https:` URL or a `data:image/` URL. An `http:` URL now fails the config check, because the page's Content-Security-Policy blocks images from it. - A heartbeat monitor with a field FlareWatch doesn't know, such as a misspelt `graceSecond`, now fails the config check, as a check monitor does. Before, FlareWatch ignored it. - Heartbeat pings answer 503 when the Worker has no `HEARTBEAT_RATE_LIMIT` binding. Before, they were taken without a limit. The committed `wrangler.toml` has the binding, so keep it if you edit that file. - A monitor's page lists maintenance windows up to a year ahead. A window that starts later shows there once it is less than a year away. ### Fixed - A proxy that reports a latency that is not a finite, non-negative number is treated as an invalid response. Before, one such value made the hub drop every monitor's samples for that hour. - A failed result a proxy reports no longer carries the proxy token into the public error text. - A `notification.timeZone` the runtime does not know fails the config check. Before, it passed, and then every alert failed at send time. - A connection failure the runtime reports as "internal error" with a log reference shows as "Connection failed". The full text stays in the worker's log. - A mass outage no longer loses down alerts to the free plan's request cap. Alerts a run cannot send wait for the following runs, and waiting does not count as a try. Before, an alert past the cap counted as failed, and after 10 runs it was dropped. - `vp run dev-status-page` serves the page again. Before, the browser got a 500 for a module that imports `cloudflare:workers` on the server only. - The `text` template called a recovery "still up" when it came in the same second as the outage began. - An alert's target URL no longer carries the user name and password from a monitor's `target`. - Browsers and proxies no longer cache the page that finishes a provider sign-in and sets the session cookie. - The maintenance dialog shows why a window was refused, such as "Too many maintenance windows", instead of the raw JSON reply. - A down alert no longer goes out twice when a stalled check run reports a failed delivery after a later run took the alert over. ## 3.2.0 - 2026-10-02 The hub moves your history to its new storage once, when it first starts after the update. You can't roll back to 3.1.0 afterwards. ### Changed - A monitor that keeps going down and coming back up stays in one outage until it has been up for 15 minutes. The page shows it as down until then, and its recovery alert goes out then. The outage counts as ending when the monitor came back up. Before, each flip wrote several rows, and about 20 flapping monitors could use up the free plan's daily writes. - Error messages are stored up to their first 500 characters. - Each monitor keeps at most about 1 MB of incident history. Only a monitor whose long error keeps changing reaches that before 1,000 incidents. - Both Workers run on the 2026-09-30 compatibility date, up from 2025-11-17. Wrangler is 4.145. ### Fixed - A page view reads one row per monitor from the hub, plus a few, however long your history is. In 3.1.0 most page views still read every stored incident, because the hub forgets what it kept in memory a few seconds after each request. - A response-time chart reads 13 rows instead of 721. - A new incident reads a few dozen rows instead of up to 2,000 for a monitor with a long history. - A monitor you change from a heartbeat to a regular check no longer stays down because of the job's last state. ## 3.1.0 - 2026-10-01 ### Changed - Each check run reads about a dozen rows from the hub instead of every stored incident, so a long history no longer eats into the free plan's 5 million rows read a day. Page views and response-time charts read only what changed since the last check run. - History drops a maintenance window 90 days after it ends, the same as incidents. - A monitor you remove from the config gets its open outage closed. Its history goes after 90 days, like any other. ### Fixed - A check run that finished after a newer one no longer overwrites the newer results. - Response-time charts show only the last 12 hours, even when check runs have stopped. ## 3.0.0 - 2026-09-30 3.0 removes the code that moved 1.x installs onto 2.x. On 2.x, update as usual. If you're still on 1.x, update to 2.3.1 first, from a clone of your fork with `upstream` set up as in [Update your fork](docs/deploy.md#update-your-fork): ```bash git fetch upstream --tags git merge v2.3.1 git push ``` Once that deploy has run, merge `upstream/main` and push again. If you skip that step, the deploy stops and says so. ### Breaking changes - 3.0 no longer copies 1.x data from KV into the hub, and the monitor Worker has no KV binding anymore. If KV still holds 1.x data that was never copied, the deploy stops. - `FLAREWATCH_STATUS_PAGE_BASIC_AUTH` is no longer read. Set `visibility: 'private'` in the page config instead. If the status page still has that secret, the deploy stops and says how to remove it, so updating never opens a private page. - `/admin` and `/events` no longer redirect. Use `/login` and `/history`. - `timeout` on a monitor or a webhook must be a whole number of milliseconds from 1 to 60000. A longer one ran into the next check run. The config check names any monitor that breaks this. A webhook in the `FLAREWATCH_WEBHOOKS` secret with such a timeout still alerts, with the default timeout, and the Worker logs why. - Links in the config must be http(s) URLs or paths: a monitor's `link`, `links`, `poweredByUrl`, `logo` and `favicon`. `logo` and `favicon` can also be `data:image/` URLs. - The admin API rejects a maintenance whose `monitors` list holds anything but monitor ids. An empty list still covers every monitor. ### Changed - A monitor that fails again within 15 minutes of recovering reopens the same incident instead of starting a new one. You still get a new down alert after the full grace period, and History shows one outage. - Each monitor keeps its newest 1,000 closed incidents. - An outage sends at most 5 error-change alerts. ### Security - Only `/embed` pages can be shown in a frame on another site. - Admin API answers to scripts that sign in with the Basic header are no longer cacheable. - The sign-in and maintenance endpoints stop reading a request body at 4 KiB and 64 KiB. - Error answers from check proxies and webhooks, and Cloudflare's trace answer, are read up to 4 KiB. The trace request has a timeout now. - A TCP check that times out closes its socket. - Webhook header values are masked in the logs. - Teams alerts no longer turn a monitor's error text into links. ## 2.3.1 - 2026-09-29 ### Security - Fixed an authorization bug in the status page's admin API. Update your fork. ## 2.3.0 - 2026-09-28 ### Added - The optional `FLAREWATCH_WEBHOOKS` secret holds alert webhooks as JSON, so their URLs and tokens stay out of your fork. See [Alerts](docs/alerts.md). ### Fixed - `/embed/` shows only the status card, without the page header and footer, and `theme=light` or `theme=dark` applies to the whole frame. ## 2.2.0 - 2026-09-28 ### Added - `dependsOn` on a monitor lists the monitors it reaches its target through. While one of them is down, it sends no alert of its own, and that monitor's alert names it under "Also down". See [Alerts](docs/alerts.md#dependencies). ### Changed - FlareWatch keeps track of which outages it alerted about. A down alert that no webhook accepted is tried again on each check run, up to 10 times. A recovery alert only follows a down alert that went out, and a missed check at the end of `gracePeriod` no longer drops the alert. - A monitor that is still down when its maintenance window ends now alerts. It used to stay silent. - Outages already open when you upgrade stay silent, recovery included. After upgrading, check the status page for monitors that were down at the time. ## 2.1.0 - 2026-09-28 ### Changed - Each row on the dashboard shows the monitor's last 90 days, or the last 30 on a phone. Jobs show their recent runs. The row still opens the monitor's page. - Colors are stricter. The uptime badge turns amber below 99.9% and red below 99%, where it used to stay green down to 99%. A day turns red after about 15 minutes down, not 12 hours. ## 2.0.0 - 2026-09-28 ### Breaking changes - Monitor state lives in a Durable Object instead of KV, and every check run is saved, so latency charts get a sample every minute. The first start after the upgrade copies your 1.x history across, leaves the 1.x keys in KV in place, and adds an `imported_to_hub` key to show the copy happened. Remove `kvWriteCooldownMinutes` from `worker.ts`, or the build fails. - A check that crashes now opens an incident instead of only counting as down. - `FLAREWATCH_STATUS_PAGE_BASIC_AUTH` is gone. Set `visibility: 'private'` in the page config instead. Visitors then get the sign-in page, and the dashboard, History, monitor pages, badges, embeds and the JSON API are closed to them. While the old secret is still set on the Worker, the page stays private-only, so upgrading never opens a private page. - The `/admin` page is gone. Sign in at `/login` (`/admin` redirects there). Once signed in, the same pages show private monitors, and you add and edit maintenance windows on History. - The Events page is now History, at `/history`. Old `/events` links redirect there. - Config lives only in `packages/config`. The `CONFIG_KV` binding is no longer read, and neither is `STATE_KV` as a second name for `FLAREWATCH_STATE`. CI checks the config, so an invalid one fails before it deploys. - Everyone signed in with the password signs in once more after the upgrade. A password session now ends when `FLAREWATCH_ADMIN_BASIC_AUTH` changes. - Password sign-in needs the `LOGIN_RATE_LIMIT` binding from `wrangler.jsonc`, and refuses to run without it. - A monitor name links to its target without the credentials or query string. Set `link` to a full URL if you need them. - `responseKeyword` and `responseForbiddenKeyword` look at the first 1 MiB of a response only. ### Added - Heartbeat monitors for jobs that report in, like backups, cron scripts and CI pipelines. One goes down when a ping is late or the job reports a failure. - Private monitors, checked and alerted as usual but shown only to people signed in. - A page per monitor, with its 90-day history, a 12-hour latency chart and its incidents. - Sign in with any OpenID Connect provider or GitHub, set in `packages/config/src/access.ts`. Besides operators, you can let in members, who see private monitors but change nothing, and audiences, who see certain page groups. - Sign-in attempts are limited per IP by a rate-limit binding, so failed logins no longer use KV writes. ### Fixed - Slack, Discord, Zulip and Google Chat alerts no longer let text from a monitored site ping everyone or add links. - A failing check proxy shows as `Proxy HTTP ` on the page. Its response body goes to the Worker log, with the proxy token taken out. - Webhook errors in the Worker log no longer quote the webhook URL. - An oversized answer from a monitored site, GlobalPing or a check proxy fails that one check instead of the whole run. ## 1.1.0 - 2026-08-31 - GlobalPing TCP_PING monitors on port 443 no longer fail with a missing port error. - The shipped demo groups its monitors. - Documented that monitoring a site in the same Cloudflare zone needs a check proxy. - Dependencies updated, including TypeScript 7, Cloudflare workers-types 5, and jsdom 30. - Wrangler stays at 4.108.0 and `@cloudflare/vite-plugin` is pinned to 1.43.2. Newer versions crash the local dev server on Linux, which breaks the browser tests. - Renovate config for automated dependency updates. ## 1.0.0 - 2026-07-13 First tagged release. - Wrangler-owned Cloudflare deploys replace the older Pulumi-owned production deployment model. - ntfy notification channel, alongside Slack, Discord, Telegram, and custom webhooks. - Self-deploy setup now requires only two GitHub Actions secrets: `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`. - The deploy workflow creates or adopts the shared `flarewatch-state` KV namespace, injects it into both Worker configs, and deploys with Wrangler.