SEO Uptime

Guides · Set up

Choose who gets alerts

Send alerts by e-mail, to a Slack channel or to a webhook, and know what each one says and when it is sent.

When we send an alert

We send an alert only when something new can take pages out of Google:

  • a critical issue anywhere on the site, such as the homepage not answering, a key page turning noindex, or robots.txt blocking the whole site;
  • an error on one of your key pages;
  • an error on many pages of one page type at once, found by a crawl of the whole site: 50 pages, or 20% of that type's pages, whichever comes first.

An issue that comes back after it was fixed counts as new. Issues the site already had, issues you've hidden or put in the backlog, and warnings never send one.

We check twice before sending. We wait 2 minutes, then fetch up to five of the pages again. If they all answer normally and can be indexed by then, nothing is sent, so a deploy still rolling out wakes nobody. Something about the whole site, such as robots.txt, is sent as it is.

With Search Console connected, we also send one when Google's answer about your homepage or a key page changes: Google drops it from its index, can't fetch it, or picks another page as the main version. That answer is Google's, so we send it as Google reported it, without fetching the page again.

If we can't reach your site on two checks in a row, the same places hear about that too.

Everything else waits for the weekly summary, an e-mail every Monday at 08:00 UTC to each e-mail address in the site's alerts, but not to Slack or webhooks. It counts the issues that are new, fixed, back or still open.

Alerts and the summary start once the site is verified.

Choose where alerts go

  1. Open the site, then Settings → Alerts.
  2. Under Where alerts go, pick E-mail or Slack as the Channel, type the address or paste the Slack URL, and click Add.
  3. To add yourself, click Add me. It fills in your address, and Add saves it.

A site's alerts go to up to 10 places: e-mail addresses, Slack channels and webhooks together. An owner or admin of the team can change them.

The address someone in your team signs in with works at once. Any other address gets an e-mail asking it to confirm, with a Send me these alerts button that works for 7 days, and nothing goes there until it does. Before the site is verified, only your team's addresses can be added.

The Where alerts go panel under Settings, Alerts: who gets alerts, a field for an e-mail address or a Slack channel, and the Add button.
Settings → Alerts.

Send alerts to Slack

  1. Go to api.slack.com/apps, signed in to your Slack workspace, and click Create New App, then From scratch. Name it, for example SEO Uptime, pick the workspace, and click Create App.
  2. Open Incoming Webhooks in the menu on the left, and switch Activate Incoming Webhooks on.
  3. Click Add New Webhook to Workspace, pick the channel for the alerts, and click Allow.
  4. Copy the Webhook URL. It starts with https://hooks.slack.com/services/.
  5. In Settings → Alerts, pick Slack as the Channel, paste the URL into Slack webhook URL, and click Add.

Each alert arrives as one message: the site's name, each issue on its own line with up to five of its pages, and a link to the site in SEO Uptime.

Anyone who has the URL can post in that channel, so treat it like a password. We keep it encrypted, and the list shows only its host and last four characters.

Send alerts to a webhook

This part is for developers. In Settings → Alerts, open For developers: send alerts to a webhook. Enter the URL to post to and, for signed alerts, a Signing secret of your own, then click Add.

The URL must be a public https address. We refuse localhost, private networks and any other address that isn't on the public internet, and we don't follow redirects. Each alert is one POST of JSON, and your endpoint has 10 seconds to answer. A failed send isn't retried: the list shows "last send failed" and why.

The bodyshown formatted; it arrives as one line
{
  "site": {
    "id": 12,
    "name": "Example Shop",
    "url": "https://www.example.com/"
  },
  "crawl": {
    "id": 4182,
    "kind": "tripwire",
    "deploy": "4f9c2e1b7d0a5e3c9f8b2a6d1e0c4b7a9f3e2d1c"
  },
  "reasons": [
    {
      "kind": "critical",
      "summary": "A critical issue appeared: The homepage or a critical page is noindex, on 1 page (NOINDEX_CRITICAL_URL)",
      "subjects": ["url:https://www.example.com/pricing"]
    }
  ],
  "sent_at": "2026-10-22T09:14:03+00:00"
}
  • crawl is the check that found it. Its kind is, for example, full for a crawl of the whole site or tripwire for a check of your key pages, and its deploy is the commit when a deploy started it. It is null for an alert from Google.
  • reasons has one entry for each issue. Its subjects say what it is about, and a page starts with url:. Its kind is critical, critical_url for a key page, mass_regression for many pages of one type, or heartbeat when we couldn't reach the site. Google's are google_dropped, google_fetch and google_canonical.

Check the signature

With a signing secret, each POST carries an X-SEOUptime-Signature header: sha256= and the HMAC-SHA256 of the body, keyed with your secret, in lowercase hex. Compute it over the raw bytes you received, before you parse the JSON, and compare in constant time. sent_at is inside what is signed, so you can also refuse old deliveries.

PHPthe endpoint's script
<?php

$secret = getenv('SEO_UPTIME_SIGNING_SECRET');
$body = file_get_contents('php://input');
$expected = 'sha256='.hash_hmac('sha256', $body, $secret);
$given = $_SERVER['HTTP_X_SEOUPTIME_SIGNATURE'] ?? '';

if (! hash_equals($expected, $given)) {
    http_response_code(401);
    exit;
}

$alert = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

foreach ($alert['reasons'] as $reason) {
    error_log($alert['site']['name'].': '.$reason['summary']);
}

http_response_code(204);
Node.jsnode server.js
const { createServer } = require('node:http');
const { createHmac, timingSafeEqual } = require('node:crypto');

const secret = process.env.SEO_UPTIME_SIGNING_SECRET;

createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const body = Buffer.concat(chunks);
    const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(body).digest('hex'));
    const given = Buffer.from(req.headers['x-seouptime-signature'] ?? '');

    if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
      res.writeHead(401).end();
      return;
    }

    const alert = JSON.parse(body.toString('utf8'));
    for (const reason of alert.reasons) {
      console.log(`${alert.site.name}: ${reason.summary}`);
    }
    res.writeHead(204).end();
  });
}).listen(3000);

What an alert says

The e-mail's subject is the site's name and the first thing that went wrong, such as "Example Shop: A critical issue appeared: The homepage or a critical page is noindex, on 1 page (NOINDEX_CRITICAL_URL)". Inside are each issue in plain words, up to five of the pages it was found on, and the commit when a deploy's check found it. The See the site button opens the site in SEO Uptime, where each issue has its evidence and how to fix it.

Stop or change alerts

To change where alerts go, click Remove beside a place in Settings → Alerts, and add another.

Every alert e-mail has a Stop these alerts link, and the weekly summary a Stop these e-mails link. Either one stops both to that address, after a click on the page it opens. The list then says "stopped by the recipient". To start again, remove the address and add it once more.