Brandon Rozek

Photo of Brandon Rozek

PhD Student @ RPI studying Automated Reasoning in AI and Linux Enthusiast.

Having your Website Visible on the Fediverse

Published on

Updated on

3 minute reading time

ActivityPub is the backbone of the Fediverse. Implementing this protocol will allow one to interact with users on Mastodon, Pixelfed, Friendica, and others. Unfortunately for a static website, this protocol uses a publish-subscribe pattern. A service would need to be written in order to handle subscribers, pushing messages of published items, as well as receiving mentions. At the current state of my website, I am uninterested in creating such a service. However, I do wish to document the parts of the ActivityPub protocol I was able to implement so far using only a static website. This post extends off of Eugen Rochko’s work which I encourage you to check out.


When someone looks up a handle on Mastodon, it performs a Webfinger lookup. For example if I am looking up the user brozek@brandonrozek.com, then the service will perform a GET request at https://brandonrozek.com/.well-known/webfinger?resource=acct:brozek@brandonrozek.com.

A Webfinger request should return the handle of the user (the subject) as well as a link to its actor.

	"subject": "acct:brozek@brandonrozek.com",

	"links": [
			"rel": "self",
			"type": "application/activity+json",
			"href": "https://brandonrozek.com/brozek.json"

Now for a static site with one user, you can hardcode the Webfinger directly at .well-known/webfinger. The only issue is that many webservers determine the mime-type of a file by its extension. In this case, we’ll have to let the webserver (mine is nginx) know how to serve it.

location /.well-known/webfinger {
    default_type application/jrd+json;
    add_header Access-Control-Allow-Origin "*";


An actor defines how to interact with a user as well as its metadata. This file also needs to be served with the application/jrd+json header. Below is an example of what I have on my website as well as how it looks on Mastodon.

	"@context": [
	"id": "https://brandonrozek.com/brozek.json",
	"type": "Person",
	"inbox": "https://brandonrozek.com/inbox",
	"outbox": "https://granary.io/url?input=rss&output=as2&url=http%3A%2F%2Fbrandonrozek.com%2Fblog%2Findex.xml",
	"preferredUsername": "brozek",
	"name": "Brandon Rozek",
	"summary": "<p>Writings from <a href='https://brandonrozek.com'>https://brandonrozek.com</p></a><p>Social profile <a href='https://fosstodon.org/@brozek'>@brozek@fosstodon.org</a></p>",
	"url": "https://brandonrozek.com",
	"manuallyApprovesFollowers": false,
	"discoverable": true,
	"published": "2022-05-17T00:00:00Z",
	"icon": {
		"type": "Image",
		"mediaType": "image/jpeg",
		"url": "https://brandonrozek.com/img/avatar.jpg"
	"publicKey": {
		"id": "https://brandonrozek.com/brozek.json#main-key",
		"owner": "https://brandonrozek.com.com/brozek.json",
		"publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"

Screenshot of brozek@brandonrozek.com profile on Mastodon

Limitations and Conclusion

Notice in the previous screenshot that it says “Cancel Follow Request”. This is because my static website is not equipped to handle a follow request. Standard ActivityPub servers keep an inventory of all the users that follow their users and who each user on the server is following.

The outbox defines items that the user has published. I have it pointed to an activity JSON feed generated directly from my RSS using granary.io. Mastodon correctly displays the number of blog posts I have, but sadly it does not show any of them. I’m not entirely sure why, though my hypothesis is that the posts need to be pushed to the Mastodon server when its created. It could also be that the actor isn’t defined properly in the activity JSON feed. If you happen to know please get in touch.

Reply via Email Buy me a Coffee
Was this useful? Feel free to share: Hacker News Reddit Twitter

Published a response to this? :