Skip to content

Get started

Most read

Ship Score

The grade out of 100 of every production deploy, category by category, and what becomes of it.

Updated on 25 September 2026

On this page

Every production deploy starts at 100 and loses points according to what failed. A single grade instead of a pass / fail, because a deploy that broke the checkout and a deploy that lost its social card aren't the same news. The computation is deterministic: no model, no hidden weighting, the scale below and nothing else.

The scale

CategoryPoints removedWhat counts in it
Payment page40 per pageA payment page URL failing (Stripe.js, login form, price token assertions), or a payment journey that breaks
Non-indexable site30The indexability check failing: a site nobody can crawl answers 200 everywhere, and the bill arrives weeks later
Invisible to answer engines25The AI visibility check failing
Missing asset25 per pageA JS or CSS file of the page missing (HTTP & assets)
DNS drift20The domain changed host during the ship (Domain & DNS)
Performance budget exceeded15PageSpeed score under the minimum or LCP above the maximum (budget), once whatever the number of pages
Rendering changed beyond the threshold15The visual diff above the chosen threshold, once
OG card15 per pageAn Open Graph check failing
SSL expiring soon10The certificate expires in less than 14 days, even if the SSL check still passes
Site hygiene10 in totalSecurity, accessibility, cookies, redirects, email DNS, SEO, weight, leaks, exposed files, certificates, script integrity — capped at 10 whatever the number of failing checks
Failing URL10 per URLAny other URL in fail or error

Categories add up: 2 failing payment pages cost 80. Only site hygiene is capped — 4 missing hygienes must not cost 40 points to a site that sells very well; a grade everybody fails says nothing. The grade never goes below 0.

What you see

  • On the project's Overview: the last deploy's score, and an explanation line naming the category that cost the most, with what it actually removed — −15 OG card, −40 payment page. Nothing when the score is 100.
  • On the deploy page (deploy diff): “Ship Score · −15 OG card”, next to the commit and the T+0 / T+2 / T+8 runs.
  • In the title of the GitHub Check: PostShip · 85.
  • On the public deploy card.

When it's computed

Once, at T+0, for every known production deploy (webhook or fingerprint). The T+2 and T+8 re-checks alert but don't re-grade. A preview has no grade: half of what makes it up — certificate, indexability, DNS drift — makes no sense on a preview host.

The score is first computed on the checks and the DNS drift, then, when PageSpeed has answered, recomputed with the “budget” and “rendering” categories. It's this second score that goes in the GitHub Check.

What triggers an alert

The score itself doesn't alert: it's the checks that make it up that open incidents, under the usual conditions (rules). 2 exceptions make it blocking:

  • On Pro Plus, the minimum score turns the GitHub Check red under the threshold (80 by default), even without a clear failure.
  • A deploy freeze turns the Check red whatever the score.

Limits and plans

DiscoverProPro Plus
Score computed on every production deployYesYesYes
Budget and rendering categories (PageSpeed measurement)NoYesYes
Minimum score on the GitHub CheckNoNoYes

On Discover, the score is computed on every deploy, but the PageSpeed measurement isn't attached to it. See the plans.

Troubleshooting

Why doesn't the explanation line add up with the score?

It only names the category that cost the most, with what it removed. A score of 55 with “−25 missing asset” lost 20 points elsewhere: the deploy page lists each URL and its verdict.

Why is the score at 90 when everything is green?

The certificate expires in less than 14 days. The SSL check still passes; the “SSL expiring soon” category is independent of the verdict, because a certificate 10 days from its end is information even when everything works. See Sitemap & SSL.

Why is the last deploy's score “—”?

The deploy wasn't graded: the function was interrupted before the end, or the line predates the score's existence. The next deploy will have one.

T+2 / T+8

The 2 automatic re-checks scheduled two and eight minutes after every production deploy, and why they exist.

A webhook that says “OK” at T+0 doesn't catch the CDN or the DNS that break a few minutes later. So after every production deploy, PostShip schedules 2 complete re-checks: one at T+2 minutes, one at T+8 minutes. They catch what breaks once the CDN cache and the edge have settled — a problem invisible to the immediate check, and that the cycle wouldn't have linked to any deploy.

What PostShip does

As soon as the deploy line is written in Deployments, 2 jobs are queued in the same queue as the cycle, with their execution time as the only difference: now + 2 minutes and now + 8 minutes. The queue is picked up every 2 minutes: each re-check goes out on the first run after its time. The run that picks it up does exactly what it usually does — all the project's URLs — with the hint T+2 or T+8 instead of “Vercel deploy 14:32”.

A given deploy can only carry one re-check of each kind: a replayed webhook doesn't schedule a third one.

Re-checks are scheduled for every path that records a production deploy: Vercel, Netlify, Cloudflare Pages, the generic webhook and the fingerprint detection. A preview gets none: nothing is live.

What you see

The deploy line, in Deployments and on the deploy page, shows the 3 runs: “T+0 OK · T+2 OK · T+8 OK”. Each cell means:

ShownWhat it means
OKAll URLs passed on that run
failedAt least one URL failed on that run
pendingThe job is queued, not yet executed (or in progress)
—No re-check for this deploy, or the job errored before giving a verdict

The T+0 verdict is the line's own; T+2 and T+8 are read from the two jobs. A deploy detected by fingerprint has a T+0 that is the cycle run where the fingerprint changed.

What triggers an alert

Re-checks alert under the same conditions as the cycle (rules): a fail that opens an incident, a return to green that closes it. The message carries the mention “Since the last deploy: …”, because a deploy hint went through — that's what tells you, at 14:40, that the 500 comes from the 14:32 ship and not from elsewhere.

What changed also rings on these runs: they are deploy runs, and a title that disappears at T+8 because a cache emptied is exactly what it looks for.

A re-check doesn't recompute the Ship Score and doesn't post a new GitHub Check: the score and the Check are T+0's. Re-checks alert, they don't re-grade.

Limits and plans

DiscoverProPro Plus
T+2 and T+8 re-checks after a webhookYesYesYes
Re-checks after a deploy detected by fingerprintYesYesYes

Re-checks are included in every plan: they follow the deploys received by webhook, or detected by build fingerprint. See the plans.

Troubleshooting

Why does T+2 stay “pending” for long?

Re-checks go through the same queue as the cycle, picked up every 2 minutes: T+2 therefore goes out between two and four minutes after the deploy, T+8 between eight and ten. A busy run shifts them a little more; “pending” beyond a quarter of an hour signals a slowdown of the queue, not a problem on your site.

Why is T+8 failed when T+0 and T+2 are OK?

That's precisely the case the re-checks exist for: a cache invalidation, a DNS switch or a staged deploy that finishes landing between two and eight minutes later. Open the incident: the failing URL and the reason are there.

Why don't re-checks appear on a preview?

Deliberate. A preview is checked once, on its own address; it has neither a production CDN nor a DNS to settle.

Production really serves this deploy

After every deploy, PostShip checks that your domain serves the new version, and not the previous one left in a cache.

The host says “ready”, PostShip checks your pages, everything is green. But green on which version? A CDN cache that wasn't purged, an alias that didn't switch, a DNS still pointing to the old host: the domain serves the previous version while the whole team looks at the new one on the deploy URL. The checks pass — they check the old site — and the customer never sees what was just shipped.

This check answers the only question that matters at that moment: what your visitors see, is it really what was just deployed?

How PostShip knows

On every production deploy received by Vercel, Netlify or Cloudflare Pages webhook, PostShip reads the same page — the site root — at 2 addresses: the deploy's own URL (my-site-abc123.vercel.app, deploy-id--my-site.netlify.app, abc123.my-site.pages.dev) and your domain. Then it compares the versioned files each one loads: the scripts and stylesheets whose name carries a build fingerprint (/_next/static/…, /assets/index-9f2e1c.js). 2 pages that load the same versioned files come from the same build. On Next.js, the build id alone settles it.

3 verdicts:

VerdictWhat it means
ServedThe domain loads the new deploy's files. The line “The domain really serves this deploy” appears under the deploy.
Old20 seconds after release, the domain was still loading the previous files. Alert.
UnknownPostShip couldn't compare, and says so rather than guessing: deploy URL protected by a password, site without versioned files, host that didn't give an own URL. Nothing is displayed.

Before concluding “old”, PostShip re-reads the domain a second time, 20 seconds later: the second the host says “ready”, the alias may be in the middle of switching. 20 seconds later, it's no longer a switch, it's a cache.

Previews are not concerned: a preview has no domain to compare with.

What triggers an alert

An “old” verdict sends, on the project's Discord and Slack channels (channels) and by email to the owner, a message “Acme: the domain still serves the old version”: which deploy is ready at the host, which domain still loads the previous files, and the 3 causes to look at — CDN cache not purged, alias not switched, DNS pointing elsewhere. No outbound webhook, no incident: the page responds, it's the version that isn't right.

What to do

  • CDN cache (Cloudflare in front of Vercel, an application cache): purge it, or set it not to cache HTML.
  • Alias: at Vercel, check that the domain is assigned to production and that the deploy was promoted; at Netlify, that the deploy is “published”.
  • DNS: if the domain still points to an old host, the DNS drift will have seen it too.

Troubleshooting

The verdict is “unknown” on all my deploys

On Vercel, if deployment protection (Vercel Authentication) covers the deploy URLs, PostShip receives a 401 and can't read the page. Allow the bypass for PostShipBot or disable protection on production deploys. On a site whose scripts have no fingerprint in the name (/main.js rather than /main-9f2e1c.js), there is nothing to compare: most build tools add one by default.

An alert while the site is up to date

If your home page loads scripts that change on every request (a session id in the name, an A/B test), the two reads differ without the version differing. Write to us from Help with the site address: we look at what the page loads.

Deploy diff

What this deploy changed on every monitored page, compared with the previous deploy, and the summary to paste in a ticket.

A green verdict says the page responds. It doesn't say the title became “Untitled”, the meta description disappeared or the Stripe button no longer loads. On every production deploy, PostShip records the state of every page; the deploy page puts that state next to the previous deploy's and says what moved, worst first.

What PostShip compares

For every URL monitored over HTTP, in the order the page shows them:

FieldWhat is read
HTTP statusThe response code
TitleThe title tag
H1The first h1
Meta descriptionmeta name="description"
og:titlemeta property="og:title"
og:imagemeta property="og:image", and the image itself (archive)
Price tokenPresent or absent, only if the URL carries the payment pages price assertion

Each cell receives a verdict:

  • identical — nothing moved.
  • changed — the field changed, without reading as broken: a title touch-up, a 200 turned 301.
  • lost — the field had content and no longer has any, or it became a text that can only be a broken page: “coming soon”, “under construction”, “lorem ipsum”, “erreur” alone or followed by a code (“erreur 500”), “erreur serveur”, “erreur interne”, “error 5xx”. A status going from under 400 to 400 or more is also “lost”, as is a present price token turned absent.

Rows are sorted from worst to least serious: the one that lost its H1 is the reason for having opened the page.

Third-party domains

PostShip also records the third-party origins loaded by each page — scripts, stylesheets, iframes, images — and says in one sentence what the deploy did to them: “js.stripe.com no longer loads on /checkout.” or “cdn.segment.com appeared on /.”.

No hard-coded list of “critical” domains: what counts is what the project's history shows as stable. An origin present on the last 3 deploys is considered installed, and its disappearance is flagged; with fewer than 3 comparable deploys, nothing is declared gone. The continuous check is on Third-party scripts.

What the page shows

Open Deployments, then a deploy: “What this deploy changed”. From top to bottom:

  1. The provider (Vercel, Netlify, Cloudflare Pages, Generic webhook, Detected without webhook) and the date, followed by “first tracked deploy, nothing to compare” if there is nothing before.
  2. A banner if the ship arrived during a freeze, another if the domain moved.
  3. Social card — before / after: the two og:images side by side when it changed.
  4. The PageSpeed measurement and the visual diff of the measured pages.
  5. The third-party domains line, only when something moved.
  6. The Ship Score and its reason, the commit (link to GitHub if the repository is known), the home page's links line, and the runs “T+0 OK · T+2 OK · T+8 OK” (T+2 / T+8).
  7. The Field / Before / After table, one section per URL.

In the Deployments list, each line already carries “Broken since: …” and “Recovered since: …” — the URLs that went from green to red, or the reverse, compared with the previous deploy.

The copyable summary

Copy the summary puts in the clipboard a plain text, without table or image, ready for Slack or a client ticket:

Acme — deploy of 11 Sept 2026 14:32 · commit 3f2a9c1 · Ship Score 85/100

https://www.acme.fr/pricing
  Title: Pricing — Acme → (empty)
  Price token: present → absent

Without a change: “No content change detected on the monitored URLs.”

What triggers an alert

The diff page doesn't alert; it explains. What wakes someone is what changed, which applies the same “lost” rule to the 4 text fields at the time of the deploy, and the checks themselves (rules). A disappeared third-party origin is flagged on the deploy page; the alert on a third-party script that changes comes from Third-party scripts.

Limits and plans

DiscoverProPro Plus
Deploy diffAll URLsAll URLsAll URLs
Social card before / afterLast ship only30 days90 days
PageSpeed and visual diff on the pageNoYesYes

See the plans.

Troubleshooting

Why “first tracked deploy, nothing to compare”?

There is no previous production deploy for this project. The diff starts at the second one.

Why doesn't a URL appear in the table?

The diff only shows the URLs whose state could be read: an HTML page checked over HTTP. A sitemap, a certificate, a form or a journey have neither title nor H1; they appear in “Broken since” if they fail.

Why “changed” and not “lost” when the title is wrong?

“Lost” is reserved for a field emptied or replaced by a broken-page text. A typo, a translation, a title replaced by another title are “changed”: visible, not alarming.

Deploy archive

The share image of each deploy, kept to show the before once the original URL is gone.

The deploy diff says “og:image: changed”. It can't show what you lost: once the ship went through, the previous URL most often returns a 404, and the proof evaporates with it. So PostShip keeps the image itself, and shows both side by side on the deploy page when it changed.

What PostShip keeps

On every production deploy received by webhook, once the checks have run, PostShip downloads the og:image of every monitored HTML page and stores it in a private storage, attached to that deploy.

  • Up to 5 images per deploy, in the order of the project's URLs.
  • 2 MB maximum per image; beyond that, it isn't kept.
  • Accepted formats: PNG, JPEG, WebP, GIF.
  • 6 seconds in total, 4 seconds per image: a slow share image has no reason to delay the deploy line and the GitHub Check.

No headless browser, so no page capture: the cost isn't the same, and the share image is precisely the one whose disappearance shows nowhere else — the site keeps answering 200, and it's the link posted on LinkedIn that is empty. The page capture, for its part, comes from PageSpeed and serves the visual regression.

The image URL is the one your page declares: if it points outside your domain, it's downloaded anyway, with the same guards as any outgoing PostShip request.

What you see

On the deploy page, the Social card — before / after section only appears if the og:image of at least one page changed. Each pair shows the page URL, the “Before” image (archived at the previous deploy — failing that, PostShip tries its original URL) and the “After” image. “none” flags a side without an image: an og:image removed by this deploy shows up immediately that way.

The Open Graph check remains the judge of the tag's content; the archive only adds the visual memory.

How long

The window is the plan's: beyond it, the image is erased by a daily purge, in small batches. The diff, for its part, keeps saying that the og:image had changed — it simply can no longer show it.

DiscoverProPro Plus
Archived imagesThe last deploy only30 days90 days
Deploys concernedNone by webhook (see below)Production by webhookProduction by webhook

On Discover, “last deploy only” means that everything that isn't the most recent production deploy is erased, whatever its age. The archive is fed by the deploy webhooks, on every plan; deploys detected by fingerprint don't archive an image. The purge follows the same window as the rest of your data (retention). See the plans.

What triggers an alert

Nothing here. The archive is a memory, not a check. An absent or unreachable og:image is detected by the Open Graph check, and a lost og:title by what changed.

Troubleshooting

Why is the “Before” image missing when the diff says “changed”?

3 possible reasons: the previous deploy's image exceeded 2 MB or wasn't in an accepted format; it didn't respond within 4 seconds; or it was purged because the previous deploy left the plan's window.

Why do only my first 5 pages have an archived image?

The cap is 5 images per deploy, taken in the order of the URLs. Put at the top of the list the pages whose social card matters most: the home page, the pricing, the flagship product page.

Why doesn't the section appear when I changed my image?

The archive compares the og:image URL from one deploy to the next. If you replaced the file behind the same URL, the tag didn't change, and nothing is shown. It's also the case where the shared link keeps the old image cached at the social networks — change the file name.

What changed

Be told when a title, an H1, a description or an og:title disappears or becomes “coming soon” right after a deploy.

Uptime says 200. It doesn't say that the pricing page lost its H1, that the title became “Coming soon” or that the meta description was emptied by a half-broken render. What changed looks at these 4 fields on every HTTP check, and only wakes you when a deploy damaged them — never for a touch-up.

What PostShip checks

On every HTTP check of an HTML page, PostShip reads and keeps 4 fields: the title, the first h1, the meta description and the og:title. It compares them with what it had kept for that URL on the previous run.

A field counts as mutated only if, at the same time:

  • it was filled before;
  • and now it is empty, or its new content contains “coming soon”, “under construction”, “erreur” or “error 5xx”.

A title replaced by another title, a typo fixed, a translation: nothing. The radar looks for what is broken, not what is different — the “different” is on the deploy diff page.

When it rings, when it stays quiet

The radar only alerts on a deploy run: the T+0 check triggered by a webhook or by the build fingerprint, and the T+2 and T+8 re-checks. On the ordinary cycle or on a “Check now”, the fields are read and memorised, but no alert goes out, even if the title disappears — only a ship can be blamed, and that's what makes the alert readable: you already know which deploy to look at.

The memorised state is always the last run's. A page that loses its H1 on a Tuesday evening on the cycle will therefore not ring on Thursday at the next deploy: the field was already empty “before”.

What triggers an alert

An alert of kind Content changed, independent of the page's verdict: it can answer 200 and be flagged. It goes out on the same channels as the outages (channels), with one line per affected field:

Title: “Pricing — Acme” → empty
H1: “Our plans” → “Coming soon”

In the email, the header counts “1 changed” next to the failures and recoveries, and the message carries the mention “Since the last deploy: …”.

The radar doesn't lower the Ship Score and doesn't open an incident: the page responds, it's its content that moved.

Set it up

  • Account → Notifications, “Email alerts” section: the Content changed after a deploy switch (“A title, a share image or a price changed without anything breaking”) turns off this alert by email, for you only. It's ticked by default.
  • Discord, Slack and the outbound webhook are set project by project in Integrations; they receive mutations like outages.
  • The silence rules — quiet hours, maintenance, muted URL — also apply to mutations.

There is nothing else to set: the list of fields and suspicious words isn't editable.

Limits and plans

DiscoverProPro Plus
Fields read on every HTTP checkYesYesYes
Alert on a deploy detected by fingerprintYesYesYes
Alert on a deploy received by webhookYesYesYes

The radar isn't reserved to a plan: what changes with the plan is how PostShip learns about your deploys (plans).

Troubleshooting

Why no alert when my title disappeared?

Either the disappearance didn't happen on a deploy run; or the field was already empty on the previous run; or the page isn't checked over HTTP (a sitemap, a form or a journey have no page surface).

Why an alert for the word “erreur” in a legitimate title?

The pattern is deliberately broad: “erreur” alone is enough. A page whose title contains that word by nature (“Error handling”) will ring if that title replaces another at deploy. Untick the email alert if it bothers you; the deploy diff, stricter, won't class it as “lost”.

Why does the alert arrive at T+8 and not at T+0?

Because the field disappeared between the two: a cache that emptied, a different render depending on the node. That's the reason the re-checks exist.