Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/config/database.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -469,4 +469,5 @@
   ensureColumn('posts', 'paid', 'INTEGER DEFAULT 0');        // paid post (klonkt-demo-aki)
   ensureColumn('posts', 'paid_min_cents', 'INTEGER');        // required support; null = owner default
+  ensureColumn('paid_patreon', 'patreon_url', 'TEXT');       // owner's public Patreon page → "Word supporter" link (klonkt-demo-aki)
   ensureColumn('ap_outbox', 'visibility', 'TEXT');  // 'direct' = private mention, never Public (shaer-tqc)
   ensureColumn('ap_outbox', 'to_actors', 'TEXT');   // JSON array of recipient actor URIs for direct notes
Index: src/routes/admin-paid.js
===================================================================
--- src/routes/admin-paid.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/routes/admin-paid.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -60,4 +60,7 @@
       accessToken: (b.access_token || '').trim() || undefined,
       refreshToken: (b.refresh_token || '').trim() || undefined,
+      // Empty clears it (null), a value sets it. Unlike secrets, this is not
+      // sensitive and there's a clear "remove the link" intent.
+      patreonUrl: (b.patreon_url || '').trim() || null,
       defaultMinCents: Number.isFinite(cents) ? cents : undefined,
     });
Index: src/routes/paid.js
===================================================================
--- src/routes/paid.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/routes/paid.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -54,9 +54,12 @@
   const payload = verifyBlob(String(req.query.state || ''));
   if (!payload || payload.purpose !== 'patron' || payload.siteId !== r.site.id) {
-    return res.status(400).send('Ongeldige of verlopen aanvraag. Probeer opnieuw vanaf de post.');
+    return renderPage(req, res, 'pages/paid-result', {
+      pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'expired',
+    });
   }
+  const patronUrl = PaidPatreon.patreonUrl(r.site.id);
   const code = String(req.query.code || '');
   if (req.query.error || !code) {
-    return renderPage(req, res, 'pages/paid-result', { pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'declined', postSlug: payload.post });
+    return renderPage(req, res, 'pages/paid-result', { pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false, reason: 'declined', postSlug: payload.post, patronUrl });
   }
   const membership = await PaidPatreon.verifyPatron(r.site.id, code, baseUrl(req) + '/paid/callback').catch(() => null);
@@ -66,5 +69,5 @@
     return renderPage(req, res, 'pages/paid-result', {
       pageTitle: 'Ontgrendelen', bodyClass: 'on-special', ok: false,
-      reason: active ? 'tier' : 'notpatron', neededCents: payload.cents, haveCents: cents, postSlug: payload.post,
+      reason: active ? 'tier' : 'notpatron', neededCents: payload.cents, haveCents: cents, postSlug: payload.post, patronUrl,
     });
   }
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/routes/posts.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -21,5 +21,5 @@
 import ActivityPubService from '../services/ActivityPubService.js';
 import { premiumUnlocked } from '../services/PatreonService.js';
-import { defaultMinCents as paidDefaultMinCents } from '../services/PaidPatreonService.js';
+import { defaultMinCents as paidDefaultMinCents, patreonUrl as paidPatronUrl } from '../services/PaidPatreonService.js';
 import MusicMeta from '../services/MusicMeta.js';
 
@@ -1247,4 +1247,5 @@
       pgCents: post.paid_min_cents || paidDefaultMinCents(site.id),
       pgSlug: post.slug,
+      pgPatronUrl: paidPatronUrl(site.id),
       newerPost,
       olderPost,
Index: src/services/PaidPatreonService.js
===================================================================
--- src/services/PaidPatreonService.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/services/PaidPatreonService.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -21,4 +21,5 @@
     tokenExp: row.token_exp || 0,
     defaultMinCents: row.default_min_cents || 0,
+    patreonUrl: row.patreon_url || null,
   };
 }
@@ -36,5 +37,12 @@
     tokenExp: c.tokenExp || 0,
     hasSecret: !!c.clientSecret,
+    patreonUrl: c.patreonUrl || null,
   };
+}
+
+// The owner's public Patreon page, for the "Word supporter" link. Null when unset.
+export function patreonUrl(siteId) {
+  const c = getOwnerConfig(siteId);
+  return c && c.patreonUrl ? c.patreonUrl : null;
 }
 
@@ -52,13 +60,15 @@
     tokenExp: patch.tokenExp ?? cur.tokenExp ?? 0,
     defaultMinCents: patch.defaultMinCents ?? cur.defaultMinCents ?? 0,
+    // undefined = keep (e.g. token refresh doesn't touch it); null/'' = clear.
+    patreonUrl: patch.patreonUrl !== undefined ? (patch.patreonUrl || null) : (cur.patreonUrl ?? null),
   };
   db.prepare(`INSERT INTO paid_patreon
-      (site_id, client_id, client_secret_enc, campaign_id, access_token_enc, refresh_token_enc, token_exp, default_min_cents, updated_at)
-      VALUES (?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)
+      (site_id, client_id, client_secret_enc, campaign_id, access_token_enc, refresh_token_enc, token_exp, default_min_cents, patreon_url, updated_at)
+      VALUES (?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)
     ON CONFLICT(site_id) DO UPDATE SET
       client_id=excluded.client_id, client_secret_enc=excluded.client_secret_enc,
       campaign_id=excluded.campaign_id, access_token_enc=excluded.access_token_enc,
       refresh_token_enc=excluded.refresh_token_enc, token_exp=excluded.token_exp,
-      default_min_cents=excluded.default_min_cents, updated_at=CURRENT_TIMESTAMP`)
+      default_min_cents=excluded.default_min_cents, patreon_url=excluded.patreon_url, updated_at=CURRENT_TIMESTAMP`)
     .run(
       siteId,
@@ -70,4 +80,5 @@
       merged.tokenExp || 0,
       Math.max(0, parseInt(merged.defaultMinCents, 10) || 0),
+      merged.patreonUrl || null,
     );
 }
@@ -171,5 +182,5 @@
 export default {
   getOwnerConfig, ownerStatus, saveOwnerConfig, disconnect,
-  defaultMinCents, needsRefresh, refreshCreatorToken, creatorAccessToken,
+  defaultMinCents, patreonUrl, needsRefresh, refreshCreatorToken, creatorAccessToken,
   pickCampaignMembership, verifyPatron,
 };
Index: src/views/pages/admin-paid.ejs
===================================================================
--- src/views/pages/admin-paid.ejs	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/views/pages/admin-paid.ejs	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -47,4 +47,8 @@
       <input type="text" name="campaign_id" value="<%= status.campaignId || '' %>" autocomplete="off" style="width:100%">
     </label>
+    <label>Openbare Patreon-pagina
+      <input type="url" name="patreon_url" value="<%= status.patreonUrl || '' %>" placeholder="https://www.patreon.com/jouwnaam" autocomplete="off" style="width:100%">
+      <small style="color:var(--ink-soft,#888)">De link waar bezoekers supporter kunnen worden. Getoond als "Word supporter" wanneer iemand nog niet doneert.</small>
+    </label>
     <label>Creator access token
       <input type="password" name="access_token" placeholder="blijft ongewijzigd" autocomplete="off" style="width:100%">
Index: src/views/pages/paid-gate.ejs
===================================================================
--- src/views/pages/paid-gate.ejs	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/views/pages/paid-gate.ejs	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -20,4 +20,7 @@
     <button type="button" id="pg-unlock" class="pg-btn">Ontgrendelen</button>
     <p id="pg-status" class="pg-status" hidden></p>
+    <% if (typeof pgPatronUrl !== 'undefined' && pgPatronUrl) { %>
+      <p class="pg-join">Nog geen supporter? <a href="<%= pgPatronUrl %>" target="_blank" rel="noopener">Word het op Patreon</a> en ontgrendel daarna met je passkey.</p>
+    <% } %>
   </section>
 </article>
@@ -88,3 +91,4 @@
   .pg-status { margin: 12px 0 0; opacity: .9; }
   .pg-status.is-err { color: #c0392b; }
+  .pg-join { margin: 14px 0 0; font-size: .92rem; opacity: .8; }
 </style>
Index: src/views/pages/paid-result.ejs
===================================================================
--- src/views/pages/paid-result.ejs	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ src/views/pages/paid-result.ejs	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -1,2 +1,7 @@
+<%
+  var _slug = (typeof postSlug !== 'undefined' && postSlug) ? postSlug : '';
+  var _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
+  var _patron = (typeof patronUrl !== 'undefined' && patronUrl) ? patronUrl : '';
+%>
 <section class="pr">
   <div class="pr-card">
@@ -4,5 +9,5 @@
     <% if (reason === 'notpatron') { %>
       <h1 class="pr-h1">Nog geen supporter</h1>
-      <p class="pr-sub">Je bent (nog) geen actieve supporter van deze site op Patreon. Word supporter en probeer het opnieuw.</p>
+      <p class="pr-sub">Je bent (nog) geen actieve supporter van deze site op Patreon. Word supporter en probeer het daarna opnieuw vanaf de post.</p>
     <% } else if (reason === 'tier') { %>
       <h1 class="pr-h1">Een niveau hoger nodig</h1>
@@ -11,11 +16,22 @@
         Jouw steun is nu &euro;<%= ((typeof haveCents !== 'undefined' ? haveCents : 0)/100).toFixed(2) %>. Verhoog je steun en probeer opnieuw.
       </p>
+    <% } else if (reason === 'expired') { %>
+      <h1 class="pr-h1">Aanvraag verlopen</h1>
+      <p class="pr-sub">Deze ontgrendel-link is verlopen of al gebruikt. Ga terug naar de post en probeer het opnieuw.</p>
     <% } else { %>
       <h1 class="pr-h1">Ontgrendelen afgebroken</h1>
       <p class="pr-sub">Er is niets gekoppeld. Je kunt het opnieuw proberen vanaf de post.</p>
     <% } %>
-    <% if (typeof postSlug !== 'undefined' && postSlug) { %>
-      <p><a class="pr-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>/<%= postSlug %>">Terug naar de post</a></p>
-    <% } %>
+
+    <div class="pr-actions">
+      <% if ((reason === 'notpatron' || reason === 'tier') && _patron) { %>
+        <a class="pr-btn" href="<%= _patron %>" target="_blank" rel="noopener">Word supporter op Patreon</a>
+      <% } %>
+      <% if (_slug) { %>
+        <a class="pr-btn pr-btn-ghost" href="<%= _base %>/<%= _slug %>">Terug naar de post</a>
+      <% } else { %>
+        <a class="pr-btn pr-btn-ghost" href="<%= _base %>/">Terug naar de site</a>
+      <% } %>
+    </div>
   </div>
 </section>
@@ -26,5 +42,7 @@
   .pr-ic { font-size: 38px; margin-bottom: 6px; }
   .pr-h1 { font-size: 23px; margin: 0 0 8px; }
-  .pr-sub { opacity: .85; line-height: 1.6; margin: 0 0 18px; }
+  .pr-sub { opacity: .85; line-height: 1.6; margin: 0 0 20px; }
+  .pr-actions { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
   .pr-btn { display: inline-block; padding: 11px 20px; border-radius: 10px; background: var(--accent,#6b8f71); color: #fff; text-decoration: none; font-weight: 600; }
+  .pr-btn-ghost { background: transparent; color: var(--ink,#222); border: 1px solid color-mix(in srgb, var(--ink,#000) 22%, transparent); }
 </style>
Index: test/paid-patron.test.js
===================================================================
--- test/paid-patron.test.js	(revision 603d2461f6fb57f0422bea17aad09eef157f788f)
+++ test/paid-patron.test.js	(revision c3d12a6f5721ed2faee19362b8d7e0caeea854d5)
@@ -73,4 +73,15 @@
 });
 
+test('patreonUrl: set, kept on unrelated save, cleared on empty', () => {
+  PP.saveOwnerConfig('s2', { clientId: 'c', clientSecret: 's', campaignId: '7', patreonUrl: 'https://patreon.com/x' });
+  assert.equal(PP.patreonUrl('s2'), 'https://patreon.com/x');
+  // a save that does NOT mention patreonUrl (e.g. token refresh) keeps it
+  PP.saveOwnerConfig('s2', { defaultMinCents: 200 });
+  assert.equal(PP.patreonUrl('s2'), 'https://patreon.com/x');
+  // an explicit empty value clears it
+  PP.saveOwnerConfig('s2', { patreonUrl: '' });
+  assert.equal(PP.patreonUrl('s2'), null);
+});
+
 test('deleteEntitlement removes the row (forget-passkey path)', () => {
   Passkey.storeEntitlement({ credentialId: 'cred3', siteId: 's1', publicKey: 'PK', minCents: 100 });
