Explore ActivityPub implementation and interoperability (e.g., with Mastodon) for the Indie Site project.


  • Get a feel for ActivityPub by hardcoding responses

  • Get a feel for the Feathers framework

  • Get interoperability working with Mastodon and note any special cases




  • [AP-n.i]: Section n, subsection i in W3C ActivityPub spec.
  • [AS-n.i]: Section n, subsection i in W3C Activity Streams spec.



Notes library

  • Currently lacking some ActivityPub vocabulary (and declared as looking for maintainers on the repo, although Evan Prodromou has been updating it recently). See this issue. My fork.

ActivityStreams test documents

“Collection of documents for testing activity streams parsers”


Evan Prodromou has just created a project to help unit test ActivityPub implementations. This looks very useful for our use case.

JSON Server

“Get a full fake REST API with zero coding in less than 30 seconds (seriously)”

(Might be great for testing as we develop. See for a live demo.)

Content-Type note

The content type for the JSON body parser in Express must be set to the ActivityPub content types or the body of the request will come in empty. e.g.,

app.use(express.json({type: ['application/json', 'application/activity+json', 'application/ld+json; profile=""']}))

Note: Mastodon uses the application/activity+json content type (which is should not must in the spec. Supporting both is the safest route.)

FeathersJS Notes

  • It’s currently not possible to mount a Feathers service at root. See my comment on Also see the root-service-error branch for a non-working example of how it would look if we could. Workaround: use Express middleware (see current implementation in master).

Perceived behaviour from Mastodon interactions

Initial actor request flow


  1. Actor request: expects actor object + HTTP Link header to be set pointing to WebFinger endpoint.

    Sample Link header:

    <>; rel="lrdd"; type="application/xrd+xml", <>; rel="alternate"; type="application/activity+json"
  2. WebFinger request: Expects WebFinger map from resource sepcified in the WebFinger URL in the Link header in 1 with a map to the id (url) specified in the Actor object in 1.

  3. Outbox request

  4. Following request

  5. Followers request

Follow request receipt

  1. Inbox request: Follow action


Personal Site Identifier Syntax Simplification

The current fediverse naming convention (@account@si.te) for accounts is biased towards multi-tenant federated web apps (as this is the norm for the fediverse at the moment).

For single-tenant sites (personal sites), this is unnecessarily redundant and can be simplified to the form @si.te as we are certain that @si.te is the identifier of a person and the only account on the site.


Temporary workaround

A temporary workaround is to have a well-known default account that is used to interoperate with existing systems like Mastodon.

In the spike, we’re using @person@si.te.

Examples: app.js, line 68 and app.js, line 39.

Mastodon interoperability

Although the ActivityPub specification does not include or require WebFinger, Mastodon does in order to interoperate.

So, to interoperate with Mastodon:

  1. We must include a Link header in the ActivityStreams Actor response that points to the WebFinger endpoint.

    Example: app.js, line 68

  2. Implement the WebFinger endpoint to map to the ActivityPub endpoint.

    Example: app.js, line 33

Author: Aral Balkan Last modified: 10/05/2018 Words: ~700 Reading time: 3 min