ActivityPub for developers
ActivityPub in theory and in practice
ActivityPub specification hasn't been updated since 2018. Many developers consider it incomplete and/or outdated.
- Authentication and authorization are not specified. In practice, HTTP signatures are used for authentication during server to server interactions.
- ActivityPub client to server API is underspecified. Vendor specific APIs are often used instead of it.
- WebFinger is used to discover actors. Actual object identifiers are often hidden from the end user.
This guide is an attempt to document how ActivityPub actually works.
Objects
Serialization
The default data serialization format is JSON. JSON-LD processing is not required by the ActivityPub specification, and it is not recommended due to its negative impact on performance and increased implementation complexity.
However, all top-level objects should have a valid @context value that includes https://www.w3.org/ns/activitystreams, because some applications may not be able to process objects without it.
Tip
JSON-LD context can be verified in JSON-LD playground.
Identifiers
Every published ActivityPub object must have a globally unique identifier, unless it only exists as a part of another object (e.g. an attachment or a tag).
Identifiers are RFC-3986 absolute URIs. Don't use uppercase characters in case-insensitive URI components (scheme, host).
URIs may be normalized for the purpose of comparison as described in RFC-3986. However, when referring to a remote object (e.g. in to or inReplyTo field), the exact value of its id property must be used.
If an object can not be retrieved by its identifier, return 404 Not Found.
Properties
Use compact property names:
- NO:
https://www.w3.org/ns/activitystreams#inReplyTo - NO:
as:inReplyTo - YES:
inReplyTo
Ignore properties that are not supported.
Immutable properties
Values of these properties must not change during the object's lifetime:
idtype
Types
Use compact type names:
- NO:
https://www.w3.org/ns/activitystreams#Person - NO:
as:Person - YES:
Person
Use duck typing to determine the core type of an object. E.g. object with inbox and outbox properties is an actor. The recommended algorithm is specified in FEP-2277DRAFT.
Use duck typing when processing objects. For example, an object with attributedTo and content properties can be classified as "post".
Use nominative typing when working with activities. Ignore activities with unsupported types.
Prefer nominative typing when working with collections.
type property
Don't use multiple types. Type arrays are not widely supported.
Tip
In most situations, duck typing results in better interoperability compared to multi-typing.
Values
Properties that are not marked as "functional" in Activity Vocabulary may have the following data types:
- A string.
- An array of strings.
- An object.
- An array of objects.
When setting the value of a property to an object or an array of objects, prefer full representations.
Implicit ordering
Assume attachment, tag, to, cc and url values are ordered arrays.
Extensions
Use standard ActivityStreams types when compatibility is required.
If standard property is not appropriate for your use case, create a new one. New properties don't break compatibility.
Feature detection
Use properties to signal supported operations. Example: followers property in actor document indicates that actor supports the Follow activity.
In some cases detection based on the value of type is preferable. For example, the type of activity usually maps to a feature (with the exception of general purpose activities such as Create and Add).
Links
Links can only exist as parts of other objects. They should not have an id property.
Actors
Actor is an entity that performs activities.
Identifiers
Don't make username a part of actor identifiers and don't assume that WebFinger addresses are persistent. Usernames can be changed.
Types
The value of actor's type property is normally one of the actor types defined in Activity Vocabulary. However, consumers should not reject actors with other types.
Required properties
In addition to id, type, inbox and outbox properties, actor objects should have:
preferredUsername. Its value must match the "username" part of a WebFinger address. Therefore, it shouldn't contain characters that are not allowed in a WebFinger username. Actors on a server should have unique usernames.publicKey. Represents an RSA public key used in HTTP signatures. Use FEP-521aFINAL to add more public keys.
The value of publicKey property is an object containing the following properties:
id: the identifier of the key (fragment identifier is recommended).owner: the identifier of the actor.publicKeyPem: the PEM-encoded value of the public key.
Outbox
Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:
outbox : An ActivityStreams OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.
Although outbox is required by ActivityPub specification, it is not necessary for federation. Return 404 Not Found or empty collection if outbox is not implemented.
Applications may use outbox to backfill profile data.
Updating
Actors are updated using the Update(Actor) activity.
Applications may also re-fetch a remote actor when they receive an activity.
Activities
Activities are used to synchronize data between servers.
All properties of activities are immutable. Activities can not be created, updated or deleted (via Create, Update or Delete activities). Activities should be idempotent.
Treat activities as notifications, not as commands.
Activities must specify their audience using to property.
Collections
Collections should be treated as dynamically generated views. Avoid adding properties to them those value may be changed directly (e.g. via Update(Collection) activity).
Don't use query parameters in collection identifiers. Query parameters are used to specify filters.
Authentication and authorization
At the very minimum, ActivityPub servers should implement HTTP signatures (Cavage draft). RFC-9421 is not widely supported.
All outgoing requests should be signed. POST requests (activity delivery) are usually signed by an actor representing a user. GET requests are usually signed by a server actor.
HTTP signature implementation details are documented in ActivityPub and HTTP Signatures report.
FEP-fe34DRAFT is recommended.
Audience
Any ActivityPub object can have audience, which is specified using to and cc properties. The audience is used for access control and to determine where activities should be delivered.
Activities and objects are assumed to be private if the audience is not specified. Actors and verification methods are assumed to be public. Collections are dynamically generated views and their audience is determined by the server.
Always use the fully-qualified identifier of the Public collection: https://www.w3.org/ns/activitystreams#Public.
Network
Push or pull?
ActivityPub's base mode of operation is "pull" (serving and retrieving objects). The "push" mode (delivery) is built on top of it. For example, there is no Create(Person) activity and actors must be retrieved from their origin.
Serving objects
Set the value of Content-Type header to application/ld+json; profile="https://www.w3.org/ns/activitystreams".
Retrieving objects
- Set the value of
Acceptheader toapplication/ld+json; profile="https://www.w3.org/ns/activitystreams"(required by ActivityPub standard). - Add
User-Agentheader. - Set request timeout.
- Add HTTP signature to the request, even when retrieving a public object (some servers may block unsigned requests in order to make large-scale data collection more difficult).
- Follow redirects, but set a limit. Request must be re-signed after every redirect.
- Set limit on response size.
- Implement protections against Server Side Request Forgery. At the very least, requests to localhost, private and unspecified IPv4 and IPv6 addresses must be blocked. Note that IPv6 addresses can be mapped to IPv4 addresses.
The value of Content-Type header in response must be either application/ld+json; profile="https://www.w3.org/ns/activitystreams" or application/activity+json (to prevent attacks like the one described in GHSA-jhrq-qvrm-qr36).
Delivering activities
- Set the value of
Content-Typeheader toapplication/ld+json; profile="https://www.w3.org/ns/activitystreams". - Add
User-Agentheader. - Add HTTP signature to request.
- Do not follow redirects.
Retry delivery if the server is not available or when response status code is one of the following:
- 401
- 429
- 5xx
Exponential backoff is recommended. Stop delivering if the server is unreachable for a prolonged time.
When a server responds with 410 Gone, treat it as equivalent to receiving a Delete(Actor) activity. This rule does not extend to 404 Not Found status, which might be temporary.
Receiving activities
Return 202 Accepted status code if activity is processed asynchronously.
Return 410 Gone if the user account doesn't exist or has been permanently deleted.
HTML content
Sanitize HTML content.
Values of summary and content properties are HTML by default.
In addition to sanitizing HTML content, applications may strip HTML tags that are not important to them. The set of allowed tags often depends on the object type (e.g. embedded media may be allowed in a content of an Article, but not in a content of a Note).
Tip
Information on allowed HTML tags can be found in the Support Tables.
Contentful objects
Contentul object is something that can be displayed as a "post".
Such objects should have attributedTo and content properties.
Protocol features
Following
An actor can follow another actor by sending a Follow activity.
The recipient may respond with an Accept or Reject activity. If the relationship has already been established, and a new Follow activity is received, the server should always respond with an Accept activity.
If an actor doesn't automatically send an Accept activity upon receiving a Follow, it should have manuallyApprovesFollowers property with value true.
A previously followed actor can be unfollowed with an Undo activity.
A follower can be removed with a Reject activity.
Posts
Posts are represented as Note and Article objects.
The compatibility table for different object types can be found at funfedi.dev site.
A post can be published with a Create activity. It can be later updated with an Update activity, or deleted with a Delete activity.
Translations
The contentMap property is used to specify variants of content in different languages.
Conversations
Replies
A reply should have an inReplyTo property indicating the post being replied to.
An application can retrieve the entire conversation by following inReplyTo references.
Followers-only
Followers-only posts are posts addressed to actor's followers collection.
Some applications create replies to followers-only posts addressed to the replier's followers collection. This leads to fragmented conversations where each branch has different audience.
To produce conversations with a consistent audience, applications should address replies to the followers collection of the top-level post author.
Reposts (boosts)
Reposts are represented as Announce activities.
A repost can be deleted with an Undo activity.
Groups
Groups are described in FEP-1b12: Group federationFINAL. Real implementations may deviate from that specification, but better specification doesn't exist at the moment.
Custom emojis
Custom emojis are documented in FEP-9098: Custom emojisDRAFT.
Reactions
Likes, favorites and upvotes are represented as Like activities. Dislikes and downvotes are represented as Dislike activities.
A like or dislike can be deleted with an Undo activity.
Emoji reactions are documented in FEP-c0e0: Emoji reactionsDRAFT.
Polls
Polls are documented in FEP-9967: PollsDRAFT.
Quote posts
There are two different ways to implement a "quote post":
- A simple link, proposed in FEP-e232: Object LinksFINAL. In addition to FEP-e232, several vendor-specific extensions are used (e.g.
quoteUrl). - An authorized quote, proposed in FEP-044f: Consent-respecting quote postsDRAFT.
Software compatibility table can be found at funfedi.dev site.
Migrations
Migration of followers is described in FEP-7628: Move actorDRAFT.
Relays
Relay protocols are documented in FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePubFINAL.
WebFinger
WebFinger protocol is used to translate an user@host address to the corresponding actor ID. This mechanism is documented in ActivityPub and WebFinger report.
Split-domain setup
Split-domain setup is used when the "host" part of a WebFinger address needs to be different from the host name of actor's ActivityPub server. More information can be found on WebFinger Split-Domain Canary website.
NodeInfo
NodeInfo best practices are documented in FEP-0151: NodeInfo in Fediverse Software (2025 edition)FINAL.
Miscellaneous
FEP-67ff: FEDERATION.mdFINAL is recommended.
Useful tools and resources
Related work
- Guide for new ActivityPub implementers
- ActivityPub Primer
- LitePub protocol suite
- Sequence diagrams of how ActivityPub works
References
- Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
- James M Snell, Evan Prodromou, Activity Vocabulary, 2017
- Ryan Barrett, nightpool, ActivityPub and HTTP Signatures, 2024
- a, Evan Prodromou, ActivityPub and WebFinger, 2024
- T. Berners-Lee, R. Fielding, L. Masinter, Uniform Resource Identifier (URI): Generic Syntax, 2005
- silverpill, FEP-521a: Representing actor's public keys, 2023
- silverpill, FEP-fe34: Origin-based security model, 2024