Oct 30, 2026
How to Disclose Affiliate Links in Social Posts When Using Short URLs
A short URL does two jobs at once: it makes a link fit a character limit, and it hides where the link goes. The first job is why you use it. The second is why disclosure gets harder the moment you swap a raw affiliate URL for a shortened one, and that part is easy to forget when you post from a script instead of by hand.
The disclosure has to move into the post itself, and there are three ways to make sure it gets there: a text convention, an interstitial redirect that shows the disclosure before forwarding, and a CI check that fails the build when an affiliate link goes out unlabelled.
The disclosure signal a short URL deletes
A raw affiliate URL usually announces itself:
https://shop.example.com/pricing?plan=team&ref=yourname&utm_source=socialThe ref= parameter is a tracking leak, but it does tell a careful reader that something is going on. Replace that URL with https://sho.rt/spring-sale and the signal is gone. The reader sees an opaque code and a domain they may not recognise.
Two other things disappear at the same time:
relattributes. On a blog you can mark an outbound linkrel="sponsored nofollow". Social posts are plain text; there's no HTML to attach it to, and the platform may rewrite or re-shorten your URL anyway.- The destination domain. Hovering a short link in a desktop client may show the short domain rather than the final page. On mobile there's nothing to hover.
The underlying requirement doesn't change. The FTC's Endorsement Guides (16 CFR Part 255) call for a clear and conspicuous disclosure of material connections; the UK's ASA/CMA rules take a similar line, and other jurisdictions have their own. In practice that means the disclosure has to be visible in the post itself, next to the link, in words a normal person reads as "this is an ad". Search-engine qualification via rel="sponsored" is a separate concern and does nothing for a reader deciding whether to tap.
Pattern 1: put the disclosure in the post text, before the link
This is the baseline. Everything else is a supplement to it.
Ad: I moved our schema migrations to Example and wrote up what broke.
https://sho.rt/spring-saleA few conventions that hold up in practice:
- Lead with the label. Use anything unambiguous:
Ad:,#ad,Affiliate link:. A label at the start gets seen; a hashtag buried in a block of tags does not. - Put it before the link, not after. The reader should know what the link is before they commit to tapping it.
- Don't outsource it to your bio or a reply. A disclosure the reader has to go looking for isn't conspicuous.
- Don't let a hashtag block carry it.
#ad #dev #node #typescriptreads as keyword stuffing, and the one meaningful tag disappears into the noise.
Pattern 2: make the short link show the disclosure itself
If you own the redirect, you can render a small disclosure page instead of forwarding instantly, and only forward when the visitor clicks through.
// links.js
export const links = new Map([
["spring-sale", { destination: "https://shop.example.com/pricing?ref=yourname", affiliate: true }],
["docs", { destination: "https://shop.example.com/docs", affiliate: false }],
]);// server.mjs
import express from "express";
import { links } from "./links.js";
const app = express();
app.get("/:code", (req, res) => {
const link = links.get(req.params.code);
if (!link) return res.status(404).type("text").send("Unknown link");
if (!link.affiliate) return res.redirect(302, link.destination);
res.set("X-Robots-Tag", "noindex");
res.type("html").send(`
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta property="og:title" content="Affiliate link">
<meta property="og:description" content="This is an affiliate link. I may earn a commission if you buy through it.">
<title>Affiliate link</title>
</head>
<body>
<main>
<p><strong>Affiliate link.</strong> If you buy through this link I may earn a
commission. The price you pay is the same.</p>
<p><a href="${link.destination}" rel="sponsored nofollow noopener">Continue</a></p>
</main>
</body>
</html>`);
});
app.listen(3000);Notes on the implementation:
- Escape
link.destinationbefore interpolating it into HTML, or build the page with a template engine that escapes by default. The snippet above is short enough to read but not safe for untrusted input. - Don't auto-redirect with
<meta http-equiv="refresh">. An interstitial that forwards after zero seconds is a redirect with extra steps; the whole point is that the person sees the disclosure. - Keep
rel="sponsored nofollow"on the outbound anchor. Inside your own HTML it's the one place the machine-readable signal still exists. - Set
noindex. A thin disclosure page shouldn't compete with the destination in search results.
The trade-off is an extra tap, which costs you some clicks. If the link is the entire point of the post, pattern 1 is usually enough. Use the interstitial when the link travels on its own: in a bio, a newsletter, a QR code, anywhere the surrounding text may not follow it.
Pattern 3: check what the preview card says
Most social clients turn a posted URL into a preview card. If the card is what people see, the disclosure has to be in it. The og:title and og:description in the snippet above do that for the interstitial page. If the preview fetches the destination directly, you can't control the wording, which is another argument for not letting an affiliate URL be the thing that gets previewed.
Test it: paste the short link into a private message or an unpublished draft and read what the card says before you publish.
Automate the check in CI
Hand-checking every draft doesn't scale past a few posts a week. Because the redirector already knows which codes are affiliate links, the same links.js map can drive a pre-publish check over your drafts:
// scripts/check-disclosures.mjs
// Usage: node scripts/check-disclosures.mjs drafts/*.md
import { readFile } from "node:fs/promises";
import { links } from "../links.js";
const SHORT_LINK = /https?:\/\/sho\.rt\/([A-Za-z0-9_-]+)/g;
const DISCLOSURE = /(#ad\b|\bad:|\baffiliate link\b|\bpaid link\b|\bsponsored\b)/i;
const WINDOW = 140; // characters of context required before the link
let failed = false;
for (const file of process.argv.slice(2)) {
const text = await readFile(file, "utf8");
for (const match of text.matchAll(SHORT_LINK)) {
const link = links.get(match[1]);
if (!link?.affiliate) continue;
const start = match.index ?? 0;
const context = text.slice(Math.max(0, start - WINDOW), start);
if (!DISCLOSURE.test(context)) {
failed = true;
const line = text.slice(0, start).split("\n").length;
console.error(`${file}:${line} affiliate link without a nearby disclosure`);
}
}
}
process.exit(failed ? 1 : 0);Wire it into the same pipeline that lints your markdown:
- name: Check affiliate disclosures
run: node scripts/check-disclosures.mjs drafts/*.mdThe window is deliberately crude. A regex can't judge whether a disclosure is clear and conspicuous, only whether one exists within a reasonable distance of the link. That's enough to catch the real failure mode, which is forgetting entirely.
Verify the redirect actually behaves
After deploying, check headers rather than the rendered page:
curl -sI https://sho.rt/spring-sale | grep -iE '^(HTTP/|location|x-robots-tag)'For an affiliate code you want no Location header, a 200, and x-robots-tag: noindex. A 302 means the affiliate branch isn't being hit, usually because the affiliate flag is missing from the map.
Edge cases worth deciding in advance
- Character limits. An affiliate URL with tracking parameters can eat most of a post. Budget the disclosure first (
Ad:is four characters) and shorten what's left. - Threads. Repeat the disclosure in every post that carries a link, not just the first one.
- Image-only posts. The disclosure belongs in the text of the post containing the image, not only in your profile.
- Quote posts and reposts. Your disclosure doesn't travel with someone else's quote of your link. Assume it's lost and don't rely on it.
- Multiple links. One clear disclosure at the top covering the whole post is normally fine; just make sure it isn't separated from the first link by a wall of text.
#AffiliateMarketing #WebDev #NodeJS #SocialMedia #Compliance
