HTTP and assets
The default check — the page responds, with the right status, within the timeout, and its JS and CSS files still exist.
Updated on 25 September 2026
On this page
- What PostShip checks
- Set it up
- What triggers an alert
- Limits and plans
- Troubleshooting
- Why is the page failing when it opens in my browser?
- Why isn't the expected status respected when the server returns 200?
- Why is the expected content missing when it's on the page?
- Expected content
- What PostShip checks
- Set it up
- What triggers an alert
- Limits and plans
- Troubleshooting
- Redirects
- What PostShip checks
- Set it up
- What triggers an alert
- Limits and plans
- Troubleshooting
An HTTP target is what PostShip adds when you paste a URL without specifying anything. It requests the page like a visitor, reads the status, measures the response time, looks for the expected content, then checks that the scripts and stylesheets referenced by the page respond too. It's that last part that catches the classic deploy: the page seems to stay green, but it loads an app.3f2a.js that no longer exists.
What PostShip checks
- The request. A
GETwith the User-AgentPostShipBot/0.1 (+https://postship.fr), a 12-second timeout, and 5 redirects at most — beyond that, the run is in error “Redirect loop.”. Each hop is re-checked: PostShip never follows a redirect to a private address. The body is read up to 512 KB. - The status. It must be exactly the expected status — 200 by default. A 301 or a 302 are followed, so the status compared is the last response's.
- The TTFB. The time to the first byte of the response, measured from PostShip's servers. It's always recorded (Performance page) and only becomes a failure if you set a latency threshold.
- The expected content. “Must contain” and “Must not contain”, compared the way a reader would — detail on Expected content.
- The HTML. On a
text/htmlresponse, PostShip records the title, the description, the canonical, theh1and theog:title(they feed what changed and the deploy diff) and checks that each JSON-LD block is valid JSON. A missing title or description are never a failure here: many legitimate pages have none. - The assets. The page's
script srcandlink rel="stylesheet", same origin and https only (third-party scripts have their own check: Third-party scripts), at most 15, in document order. Each receives aHEAD— aGETif the server answers 405 — under the same 12-second timeout as the page. An asset is broken if it doesn't respond, answers 400 or more, or answerstext/html(a framework's 404 page, served as 200, instead of the file).
Set it up
When adding (Project → URLs → “Monitor…” → “A page” → “The page responds”, “What we expect from the page” screen), or afterwards on the URL's detail:
- Expected status: from 100 to 599. Useful to monitor that an old address redirects properly (expect 200 after following) or that a private page answers 401 without a header.
- Must contain / Must not contain: 200 characters each.
- Header name and value (private page): for a page behind a shared secret. 4 names are accepted —
Authorization,X-Monitoring-Key,X-Health-Token,X-Api-Key— and nothing else: aHostor aCookiewould allow hijacking the request or replaying a session. The header is only sent to the origin host, never to a redirect to another host, never to the assets. The value is stored out of reach of your session: it's written, it isn't read back. - Latency threshold: on the URL's detail, “Latency threshold” panel.
What triggers an alert
| Code | Alert sentence |
|---|---|
| status different from expected | https://acme.fr/checkout answers 503. |
asset:404:/_next/static/app.js | The page answers 200 but a static file is missing: /_next/static/app.js. |
asset:error:/style.css | The file didn't respond at all (network, timeout). |
expect_contains:Add to cart | “Add to cart” no longer appears on the page. |
expect_not_contains:Error 500 | “Error 500” appears on the page, and shouldn't. |
ttfb_depasse:800:1243 | The page took 1243 ms to respond, beyond the 800 ms threshold. |
json_ld_syntax_error | The JSON-LD contains a syntax error. |
html_unparsable | The returned HTML is unreadable. |
A run in error (timeout, loop, DNS) also opens an incident, with the reason in plain words: https://acme.fr: Timeout (12s).
The alert goes out at the transition — when the failure streak has just reached the number set in Rules — and again only if the outage changes nature. The return to green sends “Recovered”. A missing static file costs 25 points on the Ship Score.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Monitored URLs (all projects) | 5 | 25 | 100 |
| Cycle | 5 min | 2 min | 1 min (30 s probe) |
| Assets checked per page | 15 | 15 | 15 |
The check is identical on all plans. In your logs, filter on the PostShipBot User-Agent.
Troubleshooting
Why is the page failing when it opens in my browser?
Look at the URL's detail. The most frequent case: an asset referenced by a cached HTML that points to the previous build. The browser has the old file in cache, PostShip doesn't. Purge the CDN cache or wait for the next deploy.
Why isn't the expected status respected when the server returns 200?
Because PostShip follows redirects and compares the last status. A page that redirects to a login page in 200 passes; a page that redirects more than 5 times is in error.
Why is the expected content missing when it's on the page?
The body is read up to 512 KB; beyond that, the text isn't seen. The run's detail shows bodyTruncated. Choose a text that appears early in the page, or lighten the HTML (Page weight).
Expected content
Require that a text be on the page, or that it not be there — compared the way a reader would.
A page that answers 200 can be broken: a button gone, an empty template, an error message rendered cleanly with the right status. Expected content tells PostShip what must be readable on the page — and what must not be. It's a setting of an HTTP target, available on all plans.
What PostShip checks
2 fields, 2 rules:
- Must contain: the text must appear on the page. Otherwise, gap.
- Must not contain: the text must not appear. If it does, gap.
The comparison is made on the page's visible text: tags removed, scripts and styles removed, HTML entities decoded ( , é, €…), whitespace collapsed, case and accents ignored. “Add to cart” is therefore found in Add to cart, in Add <b>to</b> cart and in ADD TO CART.
The why: a search in the raw HTML would find “Add to cart” in the JavaScript bundle of a template that didn't render — precisely the outage we want to see. The visible text is what a visitor sees, and nothing else.
The case of technical markers
A text that contains <, >, =, ", {, }, [ or ] can't be prose: data-testid="cart", <meta name=, {"@type". It's searched in the raw HTML, ignoring case, because it isn't visible anywhere. It's the text you type that decides the regime — never a fallback from one regime to the other after a failure.
Set it up
2 places:
- When adding: Project → URLs → “Monitor…” → “A page” → “The page responds”, then the “What we expect from the page” screen: “Must contain” / “Must not contain”.
- Then, at any time: the URL's detail, “Expected content” panel, same 2 fields, “Save” button. The panel also shows what the last run concluded from it: “Checked on the last run: nothing to report.” or the gap's sentence.
Each field accepts 200 characters. Empty a field and save to remove the rule.
What works well: the label of a key button (“Add to cart”, “Order”), a price (“€49”), a footer word that proves the whole template rendered. What works badly: a long sentence the copywriters may tweak, or a text present only after JavaScript runs — PostShip reads the HTML as served, without a browser.
What triggers an alert
The set text travels in the gap's code, so the alert names it:
| Code | Sentence |
|---|---|
expect_contains:Add to cart | “Add to cart” no longer appears on the page. |
expect_not_contains:Lorem ipsum | “Lorem ipsum” appears on the page, and shouldn't. |
In the email or the Slack message: “Add to cart” no longer appears on the page — https://acme.fr/product. Like any HTTP failure, the alert respects the confirmation set in Rules, goes out once per outage, and “Recovered” follows the text's return.
An empty text, or one made only of spaces, is treated as no rule: it triggers nothing.
Limits and plans
Expected content is available on all plans, with no limit of its own. It only applies to HTTP targets: the steps of a payment journey have their own assertions (price, Stripe.js), and an API contract judges the shape of the JSON rather than a text.
Troubleshooting
Why is the text declared missing when I see it on the page?
3 causes, in order of frequency. The text is added by JavaScript after load — PostShip doesn't run it. The page is over 512 KB and the text is after the cut (the run's detail shows bodyTruncated). Or the text contains a technical character (=, ", <) and is therefore searched in the raw HTML, verbatim: check that it's written exactly that way there.
Can I require several texts?
One per field. To monitor 2 elements of the same page, add the page twice with different rules — each line counts in the quota — or choose the text that proves both rendered (the footer, for instance).
Does the rule apply to redirects?
Yes: PostShip follows up to 5 redirects and compares the text of the landing page. A page that redirects to a login page therefore doesn't have the expected content — which is often exactly what you want to know.
Redirects
Do the 3 addresses people type — http, www., without www. — lead to the https site?
Nobody types https://acme.fr/. You type acme.fr, or www.acme.fr, and the browser tries http first. The site is only “up” if these entrances end up at the same origin, over https. The defect this check catches has no symptom from the base itself: https://acme.fr/ answers 200, uptime is perfect, and yet half the visitors see an error page. It's a site check: created by PostShip, outside the quota, run on deploy.
What PostShip checks
2 scenarios come back endlessly:
- the DNS migration that copies the apex record and forgets the
wwwone — whoever typeswww.acme.frlands on a browser error, and nobody notices before a customer complains; - the server that answers 200 over
httpinstead of redirecting tohttps— the site exists twice, over plain HTTP and HTTPS, with 2 caches and 2 indexings, and the clear version has no padlock.
From the project's base (https://acme.fr/), PostShip builds 3 starting points and follows each to the end, in sequence — to the end even if the first fails, because it's the full picture we want to show:
| Start | What is expected |
|---|---|
http://acme.fr/ | Redirects to the https site. |
https://www.acme.fr/ | Responds, and ends up at the site. |
http://www.acme.fr/ | Responds, and ends up at the site. |
If the base is on www., the other host is the apex: the 3 starts become http://www.acme.fr/, https://acme.fr/, http://acme.fr/. “End up at the site” means arriving at the project's origin, over https, after at most 5 redirects. Plain-HTTP hops are followed with the same host guard as the rest (never to a private address); as soon as the chain reaches https, the ordinary check finishes the job.
2 special cases: an IP address or localhost has no www. variant — a single start. A project whose base is http can't be required redirects to https: the check passes with the base_en_http warning.
More than 2 redirects before arriving is a warning: each hop costs the visitor about 100 ms and a bit of ranking.
The base itself isn't retested: that's the other checks' job.
Set it up
Nothing to create. Project → URLs, “Redirects” row. The target's detail shows the 3 starts, where each arrived, in how many hops, and the reason if it doesn't end up — “Answers 200 without redirecting.” or “Ends up at https://other.com/.”.
The fixes are at your host or your DNS: an A, AAAA or CNAME record for the missing host, and a 301 redirect from http to https and from the secondary host to the main host. Vercel, Netlify and Cloudflare Pages do both as soon as a domain is added with its variant.
What triggers an alert
Gaps — the target fails:
| Code | Screen sentence | Alert sentence |
|---|---|---|
http_non_redirige | The http address doesn't redirect to the https site. | The http address no longer redirects to https — https://acme.fr/ |
www_non_redirige | The www. address doesn't redirect to the site. | — |
apex_non_redirige | The address without www. doesn't redirect to the site. | — |
www_ne_repond_pas | The www. address doesn't respond: a visitor who types it sees an error. | The www. address no longer responds — https://acme.fr/ |
apex_ne_repond_pas | The address without www. doesn't respond: a visitor who types it sees an error. | The address without www. no longer responds — https://acme.fr/ |
The http that doesn't respond is counted as a missing redirect: the visitor who types the name without the https:// gets nowhere. The host variant that doesn't respond is another defect, at the DNS, and is fixed elsewhere.
Warnings — noted, without alert:
| Code | Sentence |
|---|---|
trop_de_sauts | More than two redirects before reaching the site. |
base_en_http | The site is on http: redirects to https can't be required. |
A failure costs at most 10 points on the Ship Score, “site hygiene” category. A request budget outage on PostShip's side is never turned into “www doesn't respond”: the run is in error and postponed.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Check and verdict on Health | Yes | Yes | Yes |
| Alert sent | No | Yes | Yes |
Troubleshooting
www. redirects to the apex but is flagged “doesn't redirect to the site”
Look at where it arrives in the detail. A redirect to https://acme.fr then to /fr/ ends up — the path is free, the origin must be exactly the project's. A redirect to another domain, a parking page, or an arrival on http:// without going back up to https doesn't end up.
Why “more than 2 redirects” when I only have one?
Count from http://www.: http to https, then www. to the apex, then a trailing slash or a language — 3 hops. Make the http://www. redirect go directly to https://acme.fr/ in one hop.
My domain is at a registrar that can't redirect www.
A CNAME www to your host is enough in most cases: it's the host that will do the redirect. A page on https://www. that displays the site without redirecting isn't enough: “end up at the site” means arriving at the project's exact origin, and www.acme.fr isn't acme.fr. The site would exist twice.