Email alerts

An email when something changes suddenly on one of your sites: traffic that drops or spikes, more visits to missing pages, a goal that stops converting, or an AI agent visiting for the first time. If you want them in Slack, Discord or your own system too, each site can also send them to a webhook. Part of Pro.

Where

  • Each site chooses its own in the site's dashboard, Settings, "Email alerts": every alert on or off, and one sensitivity for all of them. A new site starts with every alert on, at medium.
  • To stop every alert email of your account at once: Settings, Emails, "Alerts". The weekly report has its own switch.
  • They go to the account's email, in the account's language, and to the site's webhook if it has one (below).

How "usual" is worked out

  • Every hour, a few minutes past the hour, SimpleData looks at the last 6 complete hours of each site (24 for goals), in the site's time zone.
  • It compares them with the same hours on the same weekday of the previous 4 weeks, and takes the median: a holiday, last month's outage or a post that went viral one week does not set what is usual. A Saturday is compared with Saturdays, not with a Tuesday.
  • Only weeks with visits in those hours count, and at least 2 are needed. A week before the site went live, with a broken tracker or with the plan's limit reached is no reference: a launch, or a repaired tracker, is not a spike. A site added less than two weeks ago gets no traffic, 404 or goal alerts yet.
  • The figures are the dashboard's: human page views (bots never count), the 404 panel, the Conversions panel and the AI traffic card. Traffic you excluded is not there either. The email links to the dashboard on exactly those hours.
  • Fixed rules and the thresholds below. No AI model decides or writes anything.

The alerts and their thresholds

The sensitivity sets the thresholds of every alert of the site. Minimum volumes are what keeps a quiet site from getting an email for noise: with 20 visits a day, six hours hold a handful of page views, and no drop of a handful is news.

AlertLowMedium (default)High
Traffic drop: page views in the last 6 hours70 % below usual, in hours that usually have 250 or more50 % below, usually 100 or more40 % below, usually 50 or more
Traffic spike: page views in the last 6 hours5 times the usual and at least 250 more3 times and at least 100 more2 times and at least 50 more
More missing pages (404) in the last 6 hoursAt least 50, and 5 times the usualAt least 25, and 4 timesAt least 10, and 3 times
Goal with no conversions in the last 24 hoursGoals that usually convert 10 times or more in those hours5 or more3 or more
New AI agentAssistants opening your pages because someone asked them (ChatGPT-User, Claude-User, Perplexity-User…)Also the crawlers assistants answer from (OAI-SearchBot, PerplexityBot…)Also crawlers collecting pages to train models (GPTBot, ClaudeBot, CCBot…)
  • 404s: if traffic rose too, the bar rises with it. Three times the visitors bringing three times the usual 404s is the same rate, and the traffic alert already says so.
  • Goals: only a goal that converted at least once in the 24 hours before those 24, so a goal that stopped is one email, not one every day. Page-visit and custom-event goals alike.
  • AI agents: by company (ChatGPT, Claude, Perplexity…), the first time anything from that company reaches the site, whatever it came for, and only if it came for a purpose the sensitivity counts. On a site's first two weeks every agent is new, so there are none then. Search engines such as bingbot are not AI agents and never alert. Agents are not visits: they are counted apart, in the AI traffic card.

When nothing is sent

  • An account that has reached its plan's page views for the month gets no traffic, 404 or goal alerts until the month ends: SimpleData is not recording its sites, and every one of them would look like a drop. The quota emails say so already.
  • If more than 30 % of the sites where a drop can be measured (and at least 5) drop at the same time, the problem is ours, not yours: no traffic, 404 or goal alert goes out for the next 6 hours (24 for goals), and our team is told instead. AI agent alerts are not affected.

How many emails

  • At most one email per site and hour, with every alert of that site in it: a broken tracker on a site with five goals is one email.
  • The same alert at most once a day, and a drop, a spike or more 404s that last, at most every 3 days: the previous weeks take that long to catch up.
  • A goal with no conversions: one email, until it converts again and stops again.
  • A new AI agent: once per agent, ever.
  • If an email cannot be sent, the next hours try again, up to 3 times, even if the hours it is about have passed.

Plans

  • Pro. On Free the settings page shows them, locked.
  • If an account goes back to Free its alerts stop, and its settings are kept for when Pro is back.
  • By email, and to a webhook if the site has one.

What the email contains

Figures, the comparison and the hours they are about, the sensitivity that applied, and links to the dashboard and to the settings. The 404 alert gives how many missing pages were visited and how many the most repeated one got, not the addresses: anyone can request a made-up address on your site, and a mail client would turn it into a link. They are on the dashboard's 404 panel, where the email's button takes you. Nothing about any visitor.

Webhook: Slack, Discord or your own server

  • Each site can have one webhook: an address that gets the same alerts as the email, as a POST, at the same moment. Site settings, "Email alerts", Webhook. Pro.
  • One POST per site and hour with every alert in it, exactly when the email goes out: the alerts turned off, the sensitivity, the pause when many sites drop at once, the month's limit and the account-wide switch (Settings, Emails, "Alerts") apply to both.
  • Only the site's owner sets it, tests it, renews its secret or removes it. A reader of the team sees that there is one, never the address.
  • On Free nothing is sent and the webhook is kept for when Pro is back; it can be removed on any plan.
  • The address and the signing secret are stored encrypted. The secret is shown once, when the webhook is created or the secret renewed; a new one stops the old one at once.
  • "Send a test" POSTs an example, marked as a test, and shows what your server answered: 3 a minute and 20 an hour per site.

Getting the address

In Slack

  1. Open api.slack.com/apps, signed in to your workspace, and choose "Create New App", then "From scratch". Give it a name (SimpleData, for example) and pick the workspace.
  2. In the menu on the left, under Features, open "Incoming Webhooks" and switch "Activate Incoming Webhooks" on.
  3. At the bottom of that page, "Add New Webhook" (or "Add New Webhook to Workspace"): choose the channel the alerts go to, and "Allow".
  4. Back on the same page, the table has the Webhook URL, which starts with https://hooks.slack.com/services/. Copy it.

The app also shows tokens and secrets on its "Basic Information" page: SimpleData needs none of them, only the webhook URL. If Slack asks for approval when you allow it, the workspace is managed by an administrator, who has to approve the app.

In Discord

  1. Open the channel's settings (the gear next to its name), then Integrations, Webhooks and "New Webhook". It takes the "Manage Webhooks" permission on the server.
  2. Change its name if you like, and "Copy Webhook URL". It starts with https://discord.com/api/webhooks/.

Then, in SimpleData

  1. Site settings, "Email alerts", Webhook: paste the address and save.
  2. "Send a test": a message marked as a test arrives in the channel, and the page shows what Slack or Discord answered.
  3. Keep the address as you would a password: whoever has it can post to that channel. If it leaks, delete the webhook in Slack or Discord, create another one and paste the new address here.

Which addresses

  • https only, on the default port (443), and a domain name: no IP address, no localhost, no user name or password in it.
  • Every address the domain points to must be on the public internet: it is checked when you save it and again before every POST. Redirects are not followed.
  • Delivered means any 2xx answer within 5 seconds. What your server answers in the body is not read.

Slack and Discord

  • Slack: paste an incoming webhook (https://hooks.slack.com/services/…). It gets the message in text, with links in Slack's format and the dashboard's preview off.
  • Discord: paste a channel webhook (https://discord.com/api/webhooks/…). It gets the message in content, 2,000 characters at most, with mentions off: nobody is pinged.
  • Any other address gets the whole JSON below, which carries text (Slack's format) and content (Discord's) too. Microsoft Teams and Google Chat do not take it as it is: put Zapier, Make or n8n in between.

What is sent

A JSON object. Its keys are a promise: new ones may appear, and none is renamed or removed. An example, a traffic drop, with the links shortened:

{
  "event": "alerts",
  "id": "3f0c2a8e-5b1d-4c7a-9e2f-6d8b1a4c0e57",
  "created_at": "2026-11-03T10:07:12.345Z",
  "test": false,
  "title": "Traffic drop on shop.example",
  "language": "en",
  "sensitivity": "medium",
  "site": { "id": "1b9d6bcd-…", "domain": "shop.example", "timezone": "Europe/Madrid" },
  "alerts": [
    {
      "id": "3ce33e010436be0c13599fdd4956c266",
      "rule": "traffic_drop",
      "title": "Traffic drop",
      "period": "2026-11-03",
      "start": "2026-11-03T04:00:00.000Z",
      "end": "2026-11-03T10:00:00.000Z",
      "dashboard_url": "https://simpledata.dev/dashboard/1b9d6bcd-…?start=…&end=…",
      "summary": "On November 3, from 05:00 to 11:00 (Europe/Madrid), shop.example had 45 page views, against a median of 120 at those hours over the previous 4 weeks: 63% fewer.",
      "figures": { "pageviews": 45, "usual": 120, "weeks": 4, "change_pct": -63 }
    }
  ],
  "dashboard_url": "https://simpledata.dev/dashboard/1b9d6bcd-…?start=…&end=…",
  "settings_url": "https://simpledata.dev/dashboard/1b9d6bcd-…/settings#alerts",
  "text": "*Traffic drop on shop.example*\nOn November 3, … 63% fewer. <https://…|See it in the dashboard>\n<https://…|Alert settings>",
  "content": "**Traffic drop on shop.example**\nOn November 3, … 63% fewer. <https://…>\nAlert settings: <https://…>"
}
KeyWhat it is
event"alerts", or "test" for "Send a test".
idThis delivery. A new one on every POST, retries included.
created_atWhen it was sent (ISO 8601, UTC).
testtrue only for "Send a test": its alert is an example, not your site's.
title, languageOne line, in the account's language ("es" or "en"), as the email.
sensitivityThe site's: low, medium or high.
siteid, domain and timezone of the site.
alerts[]One per alert, in the email's order.
alerts[].idThe same for the same alert (site, rule, subject and period): a retry carries the one you already have.
alerts[].ruletraffic_drop, traffic_spike, not_found_spike, goal_zero or ai_agent_new.
alerts[].title, summaryThe alert's name and one sentence with its figures, in the account's language.
alerts[].periodThe site's local day (YYYY-MM-DD) the hours end in; "first" for a new AI agent.
alerts[].start, endThe hours looked at (ISO 8601; the end is not included).
alerts[].dashboard_urlThe dashboard on those hours: the email's button.
alerts[].figuresNumbers. traffic_drop and traffic_spike: pageviews, usual, weeks, change_pct. not_found_spike: not_found, usual, expected, weeks, top_address (how many the most repeated missing address got). goal_zero: conversions (0), usual, previous, weeks. ai_agent_new: visits.
alerts[].goalgoal_zero only: id and name (yours, 100 characters at most).
alerts[].agentai_agent_new only: name (ChatGPT, Claude, Perplexity… from SimpleData's list; null for any other) and purpose (user, retrieval or training).
dashboard_url, settings_urlThe first alert's dashboard, and this site's alert settings.
textThe whole message, in Slack's format.
contentThe whole message, in Discord's format (2,000 characters at most).

Headers

Content-Typeapplication/json
User-AgentSimpleData-Webhook/1.0 (+https://simpledata.dev/docs/alerts)
SimpleData-Eventalerts or test
SimpleData-DeliveryThe delivery's id, as in the body
SimpleData-Signaturet=<Unix seconds>,v1=<HMAC-SHA256 in hex of "<t>.<body>" with the secret>

Checking the signature

Compute the HMAC over the body exactly as it arrived, before parsing it, and refuse signatures older than five minutes. In Node:

import crypto from 'node:crypto';

// body: the request body exactly as it arrived (a string or a Buffer), before JSON.parse.
function verifySimpleData(body, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(String(header ?? '').split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest();
  const given = Buffer.from(parts.v1, 'hex');
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

// Express: keep the raw body for the check.
app.post('/simpledata', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifySimpleData(req.body, req.get('SimpleData-Signature'), process.env.SIMPLEDATA_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  const { event, alerts } = JSON.parse(req.body);
  // event is "alerts" or "test"; alerts[].id is the same on a retry.
  res.sendStatus(204);
});

Slack and Discord do not check it: there, the address itself is the secret. Keep it as you would a password.

Retries

  • A timeout, a connection error, a 5xx, a 408 or a 429 is retried in the next hours, 3 attempts in all, even if the hours it is about have passed. Any other 4xx (401, 404, 410…) is given up at once: your server said no.
  • A redirect (3xx) is a failed delivery: it is not followed.
  • The email and the webhook do not wait for each other: a failed POST is retried without sending the email again, and a bounced email without POSTing again.
  • Very rarely (our server stopping halfway through a delivery) the same alert may be POSTed twice, an hour apart: alerts[].id tells you it is one you have.
  • The settings page shows the result of the last delivery.

What it never carries

The same as the email: figures, hours and links, and nothing a visitor can write. No page addresses (the 404 alert gives counts), no referrers, no UTM tags. An AI agent is named only if it is on SimpleData's list, the one of the AI traffic card; a goal's name is the one you gave it, cut and escaped so that it cannot become a link or a mention in Slack or Discord. Nothing about any visitor.

← Back to the docs