Skip to content

Inbox listeners

In ActivityPub, an inbox is where an actor receives incoming activities from other actors. Fedify provides a way to register inbox listeners so that you can handle incoming activities from other actors.

Registering an inbox listener

An inbox is basically an HTTP endpoint that receives webhook requests from other servers. There are two types of inboxes in ActivityPub: the shared inbox and the personal inbox. The shared inbox is a single inbox that receives activities for all actors in the server, while the personal inbox is an inbox for a specific actor.

With Fedify, you can register an inbox listener for both types of inboxes at a time. The following shows how to register an inbox listener:

import { createFederation, Follow } from "@fedify/fedify";

const federation = createFederation({
  // Omitted for brevity; see the related section for details.

  .setInboxListeners("/users/{handle}/inbox", "/inbox")
  .on(Follow, async (ctx, follow) => {
    const parsed = ctx.parseUri(follow.objectId);
    if (parsed?.type !== "actor") return;
    const recipient = await follow.getActor(ctx);
    await ctx.sendActivity(
      { handle: parsed.handle },
      new Accept({ actor: follow.objectId, object: follow }),

In the above example, the setInboxListeners() method registers path patterns for the personal inbox and the shared inbox, and the following on() method registers an inbox listener for the Follow activity. The on() method takes a class of the activity and a callback function that takes a Context object and the activity object.

Note that the on() method can be chained to register multiple inbox listeners for different activity types.


Activities of any type that are not registered with the on() method are silently ignored. If you want to catch all types of activities anyway, add a listener for the Activity class.


You can get a personal or shared inbox URI by calling the getInboxUri() method. It takes an optional parameter handle to get the personal inbox URI for the actor with the bare handle. If the handle parameter is not provided, the method returns the shared inbox URI.

Context.documentLoader on an inbox listener

The Context.documentLoader property carries a DocumentLoader object that you can use to fetch a remote document. If a request is made to a shared inbox, the Context.documentLoader property is set to the default documentLoader that is specified in the createFederation() function. However, if a request is made to a personal inbox, the Context.documentLoader property is set to an authenticated DocumentLoader object that is identified by the inbox owner's key.

This means that you can pass the Context object to dereferencing accessors[1] inside a personal inbox listener so that they can fetch remote documents with the correct authentication.

Error handling

Since an incoming activity can be malformed or invalid, you may want to handle such cases. Also, your listener itself may throw an error. The onError() method registers a callback function that takes a Context object and an error object. The following shows an example of handling errors:

  .setInboxListeners("/users/{handle}/inbox", "/inbox")
  .on(Follow, async (ctx, follow) => {
    // Omitted for brevity
  .onError(async (ctx, error) => {


Activities with invalid signatures are silently ignored and not passed to the error handler.

  1. See the Object IDs and remote objects section if you are not familiar with dereferencing accessors. ↩︎