Index: src/routes/admin-audio.js
===================================================================
--- src/routes/admin-audio.js	(revision 4e5fe295413a76ce5327e908eaa1d702d538ee59)
+++ src/routes/admin-audio.js	(revision 8ed36de2a9b6b822f55c28663bec1275f43fb70d)
@@ -23,4 +23,5 @@
 import { audioUrl } from '../services/AudioStreamService.js';
 import { mediaDir } from '../config/paths.js';
+import * as ActivityPubService from '../services/ActivityPubService.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -301,4 +302,10 @@
   if (!track) return res.redirect('/admin/audio?error=Not+found');
 
+  // Zeg de fediverse dat de track weg is, VOOR de rij verdwijnt -- zelfde
+  // volgorde en zelfde reden als bij een post (posts.js). Zonder dit blijft
+  // elke server die hem indexeerde ernaar wijzen terwijl het object 404 geeft;
+  // op de hub stond daardoor op 21-8 een track met een dode link.
+  ActivityPubService.deliverTrackDelete(site, track.track_id).catch(() => { /* best-effort */ });
+
   db.prepare('DELETE FROM audio_tracks WHERE id = ?').run(track.track_id);
   if (track.media_id) {
@@ -371,5 +378,11 @@
     if (o.media_id) db.prepare('DELETE FROM media WHERE id = ?').run(o.media_id);
   });
-  for (const o of orphans) deleteOne(o);
+  for (const o of orphans) {
+    // Ook hier aankondigen. Een wees is voor ONS een track zonder bestand, maar
+    // voor de buitenwereld was het een gewoon Audio-object dat zij hebben
+    // opgeslagen; stil weggooien laat hun kopie staan.
+    ActivityPubService.deliverTrackDelete(site, o.track_id).catch(() => { /* best-effort */ });
+    deleteOne(o);
+  }
 
   res.json({ ok: true, deleted: orphans.length });
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 4e5fe295413a76ce5327e908eaa1d702d538ee59)
+++ src/services/ActivityPubService.js	(revision 8ed36de2a9b6b822f55c28663bec1275f43fb70d)
@@ -37,5 +37,5 @@
 import { TRACK_KOLOMMEN,
   playlistOpenTracks, siteOpenTracks, openTrack, trackHostPosts,
-  buildTrackAudio, buildTrackCollection, buildTrackCreate,
+  buildTrackAudio, buildTrackCollection, buildTrackCreate, trackUri,
   buildPlaylistCollection, listPlaylistsAP, playlistLinkTags,
   buildPostTrackCollection, uitgavePost,
@@ -3107,7 +3107,20 @@
 
 // Tell followers a post is gone (Delete + Tombstone) so it's removed from their feeds.
-export async function deliverDelete(site, post) {
+/**
+ * Delete(Tombstone) voor een van onze EIGEN objecten, naar alle volgers.
+ *
+ * De romp staat apart omdat een post niet het enige is dat wij de draad op
+ * sturen. Een track is een eersterangs Audio-object met een eigen id
+ * (shaer-0nh), en die werd bij verwijderen nergens aangekondigd: de rij ging
+ * weg, het object ging 404 en elke server die hem had geindexeerd hield hem
+ * voor altijd. Op de hub kwam dat op 21-8 aan het licht als een track die naar
+ * een dode URL wees.
+ *
+ * Het object-id komt van de aanroeper. Dat moet ook wel: bij verwijderen is de
+ * rij vaak al weg, dus er valt niets meer op te zoeken.
+ */
+export async function deliverObjectDelete(site, objectId) {
   const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
-  if (!base || !site || !site.slug || !post || !post.id) return;
+  if (!base || !site || !site.slug || !objectId) return;
   const followers = fStmts().list.all(site.slug);
   if (!followers.length) return;
@@ -3115,14 +3128,29 @@
   const keys = getOrCreateKeys(site.slug);
   const me = actorId(base, site.slug);
-  const nid = noteId(base, post.id);
   const del = {
     '@context': AP_CONTEXT,
-    id: `${nid}#delete-${Date.now()}-${rid()}`,
+    id: `${objectId}#delete-${Date.now()}-${rid()}`,
     type: 'Delete',
     actor: me,
     to: [PUBLIC],
-    object: { id: nid, type: 'Tombstone' },
+    object: { id: objectId, type: 'Tombstone' },
   };
   for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, del, `${me}#main-key`, keys.private_pem);
+}
+
+export async function deliverDelete(site, post) {
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  if (!base || !post || !post.id) return;
+  return deliverObjectDelete(site, noteId(base, post.id));
+}
+
+/**
+ * Zelfde voor een track. Roep dit aan VOOR het verwijderen van de rij, net als
+ * bij een post: daarna is `id` er nog wel maar de context niet meer.
+ */
+export async function deliverTrackDelete(site, trackId) {
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  if (!base || !site || !site.slug || !trackId) return;
+  return deliverObjectDelete(site, trackUri(base, site, trackId));
 }
 
@@ -6966,5 +6994,5 @@
   buildPostTrackCollection, uitgavePost,
   buildLibrary, libraryId,
-  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate, deliverActorUpdate, resyncFeaturedPins,
+  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverObjectDelete, deliverTrackDelete, deliverUpdate, deliverActorUpdate, resyncFeaturedPins,
   feedCursor, feedChangesSince, waitForFeedChange,
   getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, buildReplyNote, getOutboxNote, getSentNotes, deliverReply, resolveRemoteNote, noteAudience, mayReadNote,
Index: src/services/music/index.js
===================================================================
--- src/services/music/index.js	(revision 4e5fe295413a76ce5327e908eaa1d702d538ee59)
+++ src/services/music/index.js	(revision 8ed36de2a9b6b822f55c28663bec1275f43fb70d)
@@ -165,4 +165,17 @@
 }
 
+/**
+ * De identiteit van een track op de draad.
+ *
+ * Staat apart omdat hij op TWEE momenten nodig is die ver uit elkaar liggen:
+ * bij het bouwen van het Audio-object, en bij het verwijderen ervan, wanneer de
+ * rij al weg is en er dus niets meer te bouwen valt. Toen dit nog inline stond,
+ * kende alleen de bouwkant de vorm en ging er bij verwijderen geen Delete uit --
+ * elke server die de track had geindexeerd hield hem voor altijd (21-8).
+ */
+export function trackUri(base, site, id) {
+  return `${actorId(base, site.slug)}/tracks/${encodeURIComponent(id)}`;
+}
+
 export function buildTrackAudio(base, site, r, opts = {}) {
   const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`);
@@ -187,5 +200,5 @@
   const a = {
     ...(opts.standalone ? { '@context': AP_CONTEXT } : {}),
-    id: `${actorId(base, site.slug)}/tracks/${encodeURIComponent(r.id)}`,
+    id: trackUri(base, site, r.id),
     type: 'Audio',
     name: r.title || 'Audio',
