Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 6bd25d164a42c0ff47b98ae2f2e006233dcd5efa)
+++ src/routes/activitypub.js	(revision 5bf63b70700b4f2b8855d87b6e8b5cc11fd5e801)
@@ -80,9 +80,13 @@
 });
 
-// ── Inbox (Phase 1 stub: accept; Follow/Accept + sig verify next step) ──
-const apJson = express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'], limit: '1mb' });
-router.post(['/ap/users/:slug/inbox', '/ap/inbox'], apJson, (req, res) => {
-  try { console.log('[AP inbox]', (req.body && req.body.type) || 'unknown', '→', req.params.slug || 'shared'); } catch { /* ignore */ }
-  res.status(202).end();
+// ── Inbox — Follow→Accept, Undo Follow (best-effort signature verify) ──
+const apJson = express.json({
+  type: ['application/activity+json', 'application/ld+json', 'application/json'],
+  limit: '1mb',
+  verify: (req, _res, buf) => { req.rawBody = buf; }, // raw body for digest verification
+});
+router.post(['/ap/users/:slug/inbox', '/ap/inbox'], apJson, async (req, res) => {
+  try { return res.status(await AP.handleInbox(req, req.params.slug || null) || 202).end(); }
+  catch (e) { console.warn('[AP inbox] error:', e.message); return res.status(202).end(); }
 });
 
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 6bd25d164a42c0ff47b98ae2f2e006233dcd5efa)
+++ src/routes/posts.js	(revision 5bf63b70700b4f2b8855d87b6e8b5cc11fd5e801)
@@ -19,4 +19,5 @@
 import { audioUrl } from '../services/AudioStreamService.js';
 import { toWebp } from '../services/ImageWebpService.js';
+import ActivityPubService from '../services/ActivityPubService.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -230,4 +231,12 @@
       ).run(HtmlSanitizerService.toPlainText(cleanContent), title || '', req.session.user.username, postId);
     } catch (e) { /* FTS index issues are non-fatal */ }
+
+    // ActivityPub: federate a freshly published public post to followers.
+    if (!fanOnly) {
+      ActivityPubService.deliverCreate(site, {
+        id: postId, slug: finalSlug, title: title || finalSlug,
+        content: cleanContent, published_at: publishedAt, created_at: now,
+      }).catch(() => { /* best-effort */ });
+    }
   }
 
