skip to main content

Host your own webmention receiver

Beyond webmention.io and static site integration

published icon  |  category icon webdesign

tags icon indieweb hugo

Yesterday’s “Webmentions Beyond Webmention.io” IndieWeb event (see notes) exposed a problem: many bloggers exploring the IndieWeb and Webmention world simply make use of webmention.io, a hosted solution that handles receiving mentions, enabling you to treat the whole system as a black box. Aaron wondered: “why are so many people using webmention.io?" - and the answer is quite obvious: because it’s easy. Just add <link rel="webmention href="https://webmention.io/something"> to your HTML header after signing up and you’re done.

The IndieWeb community sometimes forgets that the community mostly consists of tech enthusiasts who know how to program and configure stuff, while many bloggers just want a way to interact with other blogs. They discover the Webmention system, read somewhere it’s a contemporary and maybe better alternative to Pingbacks (although I contested that), and just want to “make use” of it, without losing hours and hours of fiddling with scary code.

So, is there a decent alternative? No. But their are alternatives which allow you to host your own receiver. Again, these are very much geared towards techies…

Enabling Webmentions on Hugo

Another sore point seemed to be the lack of decent guides for people to follow when trying to enable Webmentions - beyond the WM.io “standard” - on their static sites. Since I wrote a Jamstack/webmention microservice called go-jamming which is used on my blogs, I’d like to take this opportunity to show you how easy it is to do it yourself.

Step 1: Running go-jamming yourself

The service is a single binary that is super easy to run anywhere. An amd64 version binary is available in the GitHub releases page. Follow the INSTALL.md instructions if you’d like to install it as a Linux service or set up a reverse proxy through Nginx. If all goes according to plan, you’ll end up with a https://jam.yourdomain.com/webmention and pingback endpiont others can POST to. There!

Go-jamming should be an easy drop-in replacement for Webmention.io. However, since I wrote it with my mainly own needs taken into consideration, and nobody else as far as I know is running it, it could be that you require a missing feature or so. Ping me, fork the code, … and we’ll work it out.

Step 2: Retrieve mentions and store locally

There are simple GET endpoints available that allow you to fetch mentions from the go-jamming service. I store these under data/webmentions.json, enabling the Hugo template engine to access this data. We’ll get to that in a minute. How to retrieve? Issue a simple GET request.

The question is, how to fit this into a typical Hugo/Jamstack build? I have a single JS file in the root of my website that fetches this data using jam-my-stack, a few simple wrappers around services such as Lunr and go-jamming:

const mentions = await webmention.getWebmentions("brainbaking.com")
const json = JSON.stringify(mentions, null, 4)
await fsp.writeFile(`${__dirname}/data/webmentions.json`, json, 'utf-8')

There is zero magic involved in getWebmentions(): see webmention.get.js source, if you do not want to depend on jam-my-stack.

What triggers this JS script? A GitHub actions workflow. It also automatically checks in any changes! This is a great way to get notified of new mentions. git pull tells me if new entries are added into the JSON file. This saves me from implementing a stupid mail function in the service.

Step 3: Integrate webmentions in your Hugo template

Now that we have a local JSON file, things are dead simple:

{{ $mentions := (where .Site.Data.webmentions "relativeTarget" "==" $.RelPermalink) }}
{{ range $mentions }}
  <p class="p-content p-name">
    {{ .content | safeHTML }}
  </p>
  etc!
{{ end }}

See my full template file for a complete example. How do you know which properties to use? Well, a go-jamming mention looks like this:

    {
        "author": {
            "name": "Wouter Groeneveld",
            "picture": "/pictures/brainbaking.com"
        },
        "name": "",
        "content": "More nineties collecting nostalgia. After I wrote about it at https://brainbaking.com/post/2021/05/nineties-collecting-nostalgia/ my wife dug up a shoebox full of… well… judge for yourself. From left to right:Kinder Surprise figurines, Polly Pock...",
        "published": "2021-05-13T15:20:00+00:00",
        "url": "https://brainbaking.com/notes/2021/05/13h15m20s00/",
        "type": "mention",
        "source": "https://brainbaking.com/notes/2021/05/13h15m20s00/",
        "target": "https://brainbaking.com/post/2021/05/nineties-collecting-nostalgia/",
        "relativeTarget": "/post/2021/05/nineties-collecting-nostalgia/"
    },

See the go-jamming documentation for more information.

Step 4: Sending out webmentions

You can also leverage go-jamming’s built-in ability to scan your RSS to periodically send out webmentions. Again, if you use jam-my-stack, it’s as easy as writing await webmention.send("brainbaking.com") that is also triggered using the same GitHub build action.

Go-jamming does all the rest, such as:

  1. Checking whether there’s a new article in your /index.xml feed;
  2. Collecting links to send out;
  3. Checking if the links have a webmention or pingback endpoint;
  4. Sending out the mentions accordingly.

It remembers the last processed post as not to overwhelm other endpoints, so you can call send() as many times as you’d like. If you do not like jam-my-stack as a dependency, just write your own: it’s simply a PUT request!

Does this solve the issue?

Well, yes and no. There are webmention.io alternatives, such as the one I’ve written myself, but these do assume a certain level of technical proficiency. Go-jamming makes it possible to service multiple websites: in fact, my endpoint takes care of this site, jefklakscodex.com and redzuurdesem.be. But I do not want to host mentions of others, that kind of defeats the purpose of “owning your data”.

There was also talk about writing “plugins” for static site generators in the meeting yesterday. However, I don’t think a plug-and-play version of something like this is likely to ever exist: templates are inherently personal. In my opinion, the method explained in this post is as good as it gets. I do not want to conform API contracts or input/output of go-jamming to yet another (IndieWeb or otherwise) standard that eases drop-ins of webmention servers but puts more burden on the developers of those services…

I'm Wouter Groeneveld, a level 35 Brain Baker, and I love the smell of freshly baked thoughts (and bread) in the morning. I sometimes convince others to bake their brain (and bread) too.

If you found this article amusing and/or helpful, you can buy me a coffee - although I'm more of a tea fan myself. I also like to hear your feedback via Mastodon or e-mail. Thanks!

This is the first webmention I have sent in a while. Super excited! That aside, I wanted to say that I agreed with your point about webmention.io being the most convenient option. I am presently considering whether to set up a webmention receiver in...

 | by