---
title: Page integrity & defacement monitoring in WatchFor
description: Catch defacement and tampering on your key pages — new or changed scripts, forms that send data elsewhere, redirects, weakened security headers and changed text — compared with a baseline you accept.
canonical: https://watchfor.io/docs/monitors/integrity
---

# Page integrity & defacement monitoring in WatchFor

Catch defacement and tampering on your key pages — new or changed scripts, forms that send data elsewhere, redirects, weakened security headers and changed text — compared with a baseline you accept.

A **page integrity** monitor watches the pages an attacker would pick — home,
login, checkout, pricing — and tells you when one of them stops being the page
you published. Every check is compared with an accepted **baseline**, not with
the previous check, so a change keeps showing until someone on your team looks
at it.

Today's tampering is rarely a black page with "hacked by" on it. It is a
skimmer script added next to your payment form, a vendor script that quietly
serves different code, a form that now posts to another site, a redirect for
some visitors, or spam links injected into the footer. A plain HTML diff turns
every deploy into an alert; page integrity looks at what matters for security
and ignores the normal churn of a modern site.

**Target:** your site's URL, e.g. `https://example.com`. The watched pages must
be on the same site (the same registrable domain, so `www.` and other
subdomains count).

## What each check compares

| Finding | Severity | What it means |
| --- | --- | --- |
| **Unknown script source** | critical | A page loads a script from a host that is not your site, not in the baseline and not trusted. |
| **Third-party script changed** | critical | A vendor script keeps its address but now serves different code — how compromised vendors and skimmers get in. |
| **Form sends data elsewhere** | critical | A form now submits to another site. |
| **Redirects elsewhere** | critical | The page now ends up on a different host. |
| **Tamper keywords** | critical | Words such as "hacked by" or "defaced by" — or your own extra keywords — appeared on the page. |
| **Security header weakened** | critical | Content-Security-Policy, HSTS, X-Frame-Options or X-Content-Type-Options was removed, HSTS got shorter, or the CSP now allows inline, eval or any-host scripts. |
| **Page content changed** | warning | The page's visible text differs from the baseline by more than its sensitivity. |
| **Security headers changed** | warning | Another tracked security header changed without being weakened. |
| **First-party script changed** | warning | One of your own scripts serves different code — usually your own deploy. |
| **New data destination** | warning | Browser mode: the page now sends requests to a new host. |
| **New embedded frame** | warning | A new iframe from another host. |
| **New external links** | warning | Five or more new external link domains — the SEO-spam pattern. |
| **Pages unreachable** | warning | A watched page failed to load twice in a row while the others answered. |

Your own site's scripts never count as a *new source*: build hashes and renamed
chunks change on every deploy. Their **content** is still compared, as a
warning, when script content checks are on.

Tracked security headers: `Content-Security-Policy` (and its report-only
variant), `Strict-Transport-Security`, `X-Frame-Options`,
`X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`,
`Cross-Origin-Opener-Policy`, `Cross-Origin-Embedder-Policy`,
`Cross-Origin-Resource-Policy` and `X-Permitted-Cross-Domain-Policies`.
Per-request CSP nonces are ignored.

## Setup

**Create the monitor.** Pick **Page integrity** in the type picker and enter
your site's URL. The home page is added to **Pages to watch**; add the rest by
path (`/login`, `/checkout`) or use **Import from sitemap**.

**Choose a sensitivity.** How much of a page's visible text may change before
it alerts: **Low** 50 %, **Normal** 25 % (default) or **High** 10 %. The
slider icon on a page gives that page its own value — a blog index can be
relaxed while the checkout stays strict. New scripts, forms, redirects and
tamper words alert at any sensitivity.

**Leave the first check alone.** It records the baseline for every page. From
the next check on, each page is compared with it.

### Options

- **Ignore parts of the page** — up to 20 CSS selectors (`.cookie-banner`,
  `#live-prices`) for parts that change on their own. They are left out of the
  text, links, forms and frames that are compared. **Scripts are always
  checked** — a selector can never hide one.
- **Check security headers** — on by default.
- **Detect script content changes** — on by default where your plan includes
  it: every external script's code is fingerprinted, and a script that keeps
  its address but serves different code is reported. Vendor scripts that
  rotate their code on their own (some tag managers and ad pixels build a new
  file every few minutes) are recognised when you accept a baseline and marked
  *changes often — content not compared*; their source is still watched.
- **Trusted script sources** — hosts or origins that are never reported as
  new, e.g. `https://js.stripe.com`.
- **Extra keywords** — words that should never appear on your pages, on top
  of the built-in tamper words.
- **Check in a real browser** — see [Browser mode](#browser-mode).

## Baseline and accepting changes

The monitor's **Page integrity** tab lists every page with its status, the
text change and when its baseline was recorded. Open a page to see the word
diff, its scripts, forms, frames, data destinations and headers, each marked
**NEW** or **CODE CHANGED** where it differs.

- **Accept as new baseline** (per page) or **Accept all changes** — the change
  was yours. When that leaves nothing to fix — every page now matches its
  baseline — the open incident resolves at once, and its timeline says
  "Resolved — baseline accepted by" you. If another page still differs or
  could not be fetched, the incident stays open: WatchFor checks the pages
  again right away and it closes when they match. Accepting all changes asks
  for confirmation when critical findings are among them.
- **Trust this source** — on an unknown script source: adds it to the trusted
  sources, so it is never reported again on any page of the monitor.
- The baseline shows how it was set: recorded automatically from the first
  check, accepted by a teammate, accepted via the API, or accepted
  automatically after a set time.

### Accept changes after a deploy

Your own releases should not alert. Run this as the last step of your deploy
pipeline:

```bash
curl -s -X POST "https://watchfor.io/api/v1/monitors/<monitor-id>/baseline?fresh=true" \
  -H "Authorization: Bearer wf_live_YOUR_WRITE_KEY"
```

`fresh=true` checks the pages first, then accepts what that check saw — never
the version from before the deploy. The request waits for the check (usually
well under a minute) and returns the pages accepted and skipped; if an open
integrity incident has nothing left to fix it is resolved at once
(`incidents_resolved`). If the check takes longer than about 85 seconds you
get `202` with `"status": "pending"` and the accept finishes on its own when
the check lands; `GET` on the same URL shows the outcome under
`fresh_accept`. Pages that check could not fetch are skipped, never taken
from an older check.

Accept only some pages with `{"pages": ["https://example.com/pricing"]}`.
Each accept may start a check, and a monitor allows 12 of those per hour — a
fresh accept over that is refused with `429`. Use an API key with **write**
scope from **Settings → API keys**. The command is also on the monitor's Page
integrity tab, with the monitor's ID filled in. See the
[Monitors API](/docs/api/monitors#accept-the-latest-state-deploy-hook).

**Quiet during the rollout.** A finding alerts only when a second check
confirms it: the first sighting schedules a re-check about 5 minutes later,
and only if that one sees the same change does the alert fire. So a deploy
that calls the fresh accept above within about 5 minutes of its first server
switching to the new version never alerts — the re-check compares against
the new baseline and finds nothing. Accept as soon as the release is fully
out; no maintenance window is needed.

If a rollout regularly takes longer than that, accept once the first server
is live and again when the last one is, or open a short
[maintenance window](/docs/monitors/maintenance) that only mutes alerts for
the monitor while it runs.

### Accept unreviewed changes automatically

**If a change isn't reviewed, accept it after** Never (default), 1, 3, 6 or
24 hours. The alert still fires as soon as a second check confirms the change; if nobody accepts or trusts the
change in time, it becomes the new baseline, the incident gets a note saying
so and the next checks close it. The [Activity Log](/docs/organization/activity-log)
records it too.

Security findings — new scripts, changed code, forms, headers, redirects,
tamper words — always wait for a person unless you tick **Also accept
security findings**. An attacker only needs to wait that long, so prefer
**Trust this source** for vendors you know.

## Browser mode

By default each page is fetched over HTTP and its HTML is analysed. Some
tampering only exists after the page runs: a script injected by another
script, a tag manager loading a new vendor, a skimmer that sends card data
with `fetch`. **Check in a real browser** renders every page in Chrome and
compares everything it actually loaded:

- every script the page loaded, including scripts added at runtime;
- **data destinations** — the hosts the page sends data to (XHR and fetch
  requests, beacons, WebSockets, tracking pixels), reported as *New data
  destination*. A site that already serves the page a script is not a new
  destination, nor is the collector of a known tag already on the page —
  Google tag sending to `google-analytics.com`, Meta Pixel to `facebook.com`.
  Such beacons fire only when the page stays open long enough, so they would
  otherwise come and go between checks;
- **screenshots** — the check that became the baseline and any check that
  found a difference photograph the page. Open a page and choose
  **Screenshots** to see **Baseline | Now** side by side; the incident's root
  cause shows the *Now* screenshot as evidence.

Browser-mode checks run from browser-capable locations only, take longer and watch
up to 10 pages per monitor. Switching modes records a fresh baseline for every
page at the next check.

## Incidents and alerts

Each finding is a separate alert rule on the monitor's **Alerting** tab, so
one event opens one incident with a plain-words root cause ("New script
source https://cdn.example-evil.com on /checkout"). A finding repeats on every check
until it is accepted, so the first confirmed check is enough to alert; a quick
confirming re-check filters out a one-off fetch glitch.

Security findings stay open until **3 clean checks** in a row, so one clean
check from another location cannot close an incident for a skimmer served
only to some visitors. Accepting the change or trusting the source closes the
incident at the next checks.

### Status pages

Page integrity incidents are **never published to a status page
automatically**: tampering is not downtime, and the details could show
attacker hosts to the public. A component with a page integrity monitor stays
**Operational** unless someone on your team publishes an incident by hand —
see [Incident updates](/docs/status-pages/updates#auto-published-incidents).

## Plans

| Plan | Monitors | Pages per monitor | Browser mode | Shortest interval |
| --- | --- | --- | --- | --- |
| Free | — | — | — | — |
| Starter | 3 | 10 | — | 15 minutes |
| Pro | 10 | 25 | 2 monitors | 5 minutes |
| Business | 50 | 50 | 10 monitors | 5 minutes |
| Enterprise | Unlimited | 50 | Unlimited | 5 minutes |

The create form shows how many pages your plan still allows. The live numbers
are on [pricing](/pricing).

## Also via API

Create with `type: "integrity"` and the pages in `config.pages` — see
[Monitors API](/docs/api/monitors#create-a-monitor) and the exact config keys
and alert metrics in the [type catalog](/docs/api/monitor-types). Read and
accept the baseline with `GET` / `POST /v1/monitors/{id}/baseline`.

---

Canonical page: https://watchfor.io/docs/monitors/integrity · All docs: https://watchfor.io/docs · Site guide: https://watchfor.io/llms.txt
