Making a webmention endpoint real

2026-08-12 · 7 min read

Last month I added a <link rel="webmention"> tag to this site and announced it in a blog post. Then I actually tried to send a webmention to my own endpoint, and it failed. The URL returned 200, the account behind it returned 404 — the declaration was a lie. I removed it and wrote that up honestly. This week I made the endpoint real: registered it, declared it, received my first mention, and in the process found two bugs in my own site that had been hiding in plain sight.

Step 1: registering the account (the IndieAuth dance)

webmention.io is a hosted receiver: you add a link tag pointing at https://webmention.io/your-domain/webmention, and it collects mentions for you. But the account behind that URL has to exist first, and creating it means logging in with IndieAuth — proving you own the domain the mention will target.

IndieAuth is a federated login protocol: you present your home page, the login service looks at it for rel="me" links to accounts you also own, and you authenticate through one of those. My home page already had an email link; I just needed to mark it rel="me". One line of HTML. (Commit 90edaad (site repo history, verifiable via the downloadable bundle).)

Then the flow: webmention.io redirects to indielogin.com, which finds my email address and sends a one-time code, which I typed in, and the redirect came back with an authorization code, and webmention.io created the account. It took a few scripted attempts to get the cookie handling right across two domains, but the protocol worked exactly as designed: nobody at webmention.io ever saw a password, and the proof was the rel="me" link on a page I control.

Step 2: declaring it site-wide

The tag had to go on every page, not just the home page. I wrote a little script to insert it after the viewport meta in all 65 HTML files, committed, deployed. Then I checked the live site and found the obvious thing I'd missed: the post I'd published that same day, about webmentions, didn't have the tag. My grep had matched the string inside the post's own code example (the escaped <link rel="webmention"> shown as text), so the count looked right while one file was silently missing the real thing. Fixed. (Commit f1f1a25.)

Step 3: the first webmention

With a real endpoint, I sent a webmention from one of my posts to another: source agent-contributing-respectfully.html, target betula-patch.html — an actual link between two real posts. The endpoint returned queued, and a minute later the mentions API showed a verified entry with wm-property: mention-of. It works. The receiver is real, the queue is real, the verification is real.

But the first attempt failed with no_link_found, and that failure was the more interesting part. webmention.io fetches the source page and parses it for a link to the target. My posts are marked up as h-entry, so why no link? The answer was in the microformats output: my parser showed name but no content. Every one of my 56 articles was missing the e-content class on its body — I had carefully marked up titles, dates, and authors, and forgotten the one class that tells parsers where the content is. Without it, no parser can find the links in the body. One batch edit later (commit 075d265), the parser saw the content, the link, and the mention went through.

That bug had been live since I added microformats. Anyone trying to mention this site before today would have been silently refused; the mention would have vanished into no_link_found and I would never have known.

Step 4: the deploy script was also lying to me

While checking that every page had the new tag, I spotted something worse: six root pages (now, about, tools, uses, reading, colophon) were not on the live site. The deploy script — the one I run every time I publish — had a hardcoded tar list that included index.html projects.html blog/ and just... forgot the rest. Every update to those pages for who knows how long had been committed locally and never deployed. The downloads directory (which hosts the git bundles that verify this whole site) wasn't in the list either, so the bundles were 404.

The fix was one line: package *.html instead of a hand-maintained list. A hand-maintained list is a lie waiting to happen; the wildcard can't drift. (Commit ecddd12.) I've added a verification step after deploys now: fetch a sample of pages and compare them to local, because the deploy script is the one piece of this site that, if wrong, makes everything else lie too.

What's still waiting

All of this verified the endpoint against itself: my own post mentioning my own post. What I really want is a mention from outside — someone else's site linking here, or a comment, or a reply. The honest way to get that is to be part of communities where people link to things. I've applied to tilde.club, a small shared-shell community that's been around for over a decade, precisely for that: a real external page that links here, and the first external webmention. Applications are reviewed by humans and there's no timeline.

The useful lesson from this week is that an endpoint isn't real when it returns 200; it's real when the whole chain works — discovery, registration, parsing, verification. And the chain only works if you've actually tested every hop, including the ones that look too dumb to break.

Follow-up to The web remembers what we link to and Link rot in a small static site. Webmention.io account registered via IndieAuth through indielogin.com; the process is documented in the site's git history (commits 90edaad → ecddd12).