Index: src/services/StatsService.js
===================================================================
--- src/services/StatsService.js	(revision 2a17f0a714ea476a1c6090cbbad470e9a2d46104)
+++ src/services/StatsService.js	(revision 0f48411a45b8908eb1849b383af40a3b658f68c9)
@@ -47,8 +47,14 @@
 // Skip known bots/crawlers + link-preview fetchers + scripts so they don't inflate
 // view/visitor-day counts. Empty UA = almost always automated.
-const BOT_RE = /bot|crawl|spider|slurp|mediapartners|bingpreview|facebookexternalhit|whatsapp|telegram|discord|twitter|linkedin|embedly|pinterest|redditbot|applebot|petalbot|yandex|baidu|duckduckbot|semrush|ahrefs|mj12|dotbot|uptimerobot|pingdom|statuscake|headless|lighthouse|gptbot|claude|ccbot|perplexity|bytespider|amazonbot|googleother|google-read-aloud|python-requests|scrapy|curl|wget|axios|node-fetch|go-http|java\/|okhttp|libwww|httpclient/i;
+const BOT_RE = /bot|crawl|spider|slurp|mediapartners|bingpreview|facebookexternalhit|whatsapp|telegram|discord|twitter|linkedin|embedly|pinterest|redditbot|applebot|petalbot|yandex|baidu|duckduckbot|semrush|ahrefs|mj12|dotbot|uptimerobot|pingdom|statuscake|headless|lighthouse|gptbot|claude|ccbot|perplexity|bytespider|amazonbot|googleother|google-read-aloud|python-requests|scrapy|curl|wget|axios|node-fetch|go-http|java\/|okhttp|libwww|httpclient|mastodon|pleroma|akkoma|misskey|calckey|firefish|friendica|hubzilla|lemmy|pixelfed|peertube|gotosocial|honk|http\.rb|activitypub/i;
 function isBot(req) {
   const ua = (req && req.headers && req.headers['user-agent']) || '';
   if (!ua) return true;          // empty UA = script/bot
+  // A signed request or an ActivityPub content-negotiation is BY DEFINITION a
+  // server fetching, not a reader (Robins vraag, 31-7): one boosted post made
+  // every fediverse instance's link-preview fetch count as a unique visitor.
+  const h = (req && req.headers) || {};
+  if (h['signature']) return true;
+  if (/application\/(activity|ld)\+json/i.test(String(h['accept'] || ''))) return true;
   return BOT_RE.test(ua);
 }
Index: test/stats-bots.test.js
===================================================================
--- test/stats-bots.test.js	(revision 0f48411a45b8908eb1849b383af40a3b658f68c9)
+++ test/stats-bots.test.js	(revision 0f48411a45b8908eb1849b383af40a3b658f68c9)
@@ -0,0 +1,48 @@
+// Statistics count READERS, not servers (Robins vraag, 31-7). Fediverse
+// link-preview fetchers (Mastodon's http.rb and friends) hit the human post
+// URL when a post gets boosted; without these guards every instance counted
+// as a pageview AND a unique visitor.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://test.example';
+
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+dbMod.initializeDatabase();
+const Stats = await import('../src/services/StatsService.js');
+
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
+  .run('u1', 'robin', 'r@t', 'x', 'god');
+db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'me', 'Me', 'u1');
+
+const pv = () => db.prepare('SELECT COALESCE(SUM(pageviews),0) AS n FROM stat_daily').get().n;
+const fakeReq = (headers = {}, ip = '203.0.113.7') => ({ ip, headers, session: {} });
+
+test('a human browser counts', () => {
+  const before = pv();
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Mozilla/5.0 (iPhone; like Mac OS X) Safari/605.1' }));
+  assert.equal(pv(), before + 1);
+});
+
+test('a fediverse preview fetcher does not count', () => {
+  const before = pv();
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'http.rb/5.1.1 (Mastodon/4.2.10; +https://koffieengaar.nl/)' }));
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Pleroma 2.6.2; https://p.example <admin@p.example>' }));
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Misskey/2024.10 (https://m.example)' }));
+  assert.equal(pv(), before, 'server fetches are not readers');
+});
+
+test('a SIGNED request never counts, whatever the UA claims', () => {
+  const before = pv();
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Mozilla/5.0 totally-a-browser', signature: 'keyId="x",signature="y"' }));
+  assert.equal(pv(), before);
+});
+
+test('ActivityPub content-negotiation never counts either', () => {
+  const before = pv();
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Mozilla/5.0 totally-a-browser', accept: 'application/activity+json' }));
+  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Mozilla/5.0', accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }));
+  assert.equal(pv(), before);
+});
